Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Unit Tests! #7

Merged
merged 5 commits into from

3 participants

@Havvy

You wanted some unit tests for this? Here you go!

Also, made it packaged for node.js, though I left the original streams.js on the top level of the project that doesn't include the module.exports line in /lib/index.js

@dionyziz dionyziz merged commit 97c9d47 into dionyziz:master
@Garciat

This method always throws (for finite streams, of course).

One fix would be:

toString: function () {
    if (this.empty()) {
        return '[stream empty]';
    }

    return '[stream head: ' + this.head() + '; tail: ' + this.tail() + ']';
}
@Garciat

The following works OK for "pure" streams such as the sieve stream.

if (typeof this.tailMemo !== 'undefined') {
    return this.tailMemo;
}
return this.tailMemo = this.tailPromise();

I.e., a linked list.

However, that would make no sense for something like:

Stream.rand = function () {
    return new Stream(Math.random(), Stream.rand);
};

Make it optional through a derived class?

function MemoStream() {
    Stream.apply(this);
    this.tailMemo = null;
}

MemoStream.prototype = new Stream();

MemoStream.prototype.tail = function() {
    if ( this.empty() ) {
        throw new Error('Cannot get the tail of the empty stream.');
    }
    if (this.tailMemo === null) {
        this.tailMemo = this.tailPromise()
    }
    return this.tailMemo;
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
This page is out of date. Refresh to see the latest.
Showing with 387 additions and 0 deletions.
  1. +2 −0  .gitignore
  2. +226 −0 lib/index.js
  3. +26 −0 package.json
  4. +133 −0 spec/stream.spec.js
View
2  .gitignore
@@ -0,0 +1,2 @@
+.project
+
View
226 lib/index.js
@@ -0,0 +1,226 @@
+function Stream( head, tailPromise ) {
+ if ( typeof head != 'undefined' ) {
+ this.headValue = head;
+ }
+ if ( typeof tailPromise == 'undefined' ) {
+ tailPromise = function () {
+ return new Stream();
+ };
+ }
+ this.tailPromise = tailPromise;
+}
+
+Stream.prototype = {
+ empty: function() {
+ return typeof this.headValue == 'undefined';
+ },
+ head: function() {
+ if ( this.empty() ) {
+ throw 'Cannot get the head of the empty stream.';
+ }
+ return this.headValue;
+ },
+ tail: function() {
+ if ( this.empty() ) {
+ throw 'Cannot get the tail of the empty stream.';
+ }
+ // TODO: memoize here
+ return this.tailPromise();
+ },
+ item: function( n ) {
+ if ( this.empty() ) {
+ throw 'Cannot use item() on an empty stream.';
+ }
+ var s = this;
+ while ( n != 0 ) {
+ --n;
+ try {
+ s = s.tail();
+ }
+ catch ( e ) {
+ throw 'Item index does not exist in stream.';
+ }
+ }
+ try {
+ return s.head();
+ }
+ catch ( e ) {
+ throw 'Item index does not exist in stream.';
+ }
+ },
+ length: function() {
+ // requires finite stream
+ var s = this;
+ var len = 0;
+
+ while ( !s.empty() ) {
+ ++len;
+ s = s.tail();
+ }
+ return len;
+ },
+ add: function( s ) {
+ return this.zip( function ( x, y ) {
+ return x + y;
+ }, s );
+ },
+ zip: function( f, s ) {
+ if ( this.empty() ) {
+ return s;
+ }
+ if ( s.empty() ) {
+ return this;
+ }
+ var self = this;
+ return new Stream( f( s.head(), this.head() ), function () {
+ return self.tail().zip( f, s.tail() );
+ } );
+ },
+ map: function( f ) {
+ if ( this.empty() ) {
+ return this;
+ }
+ var self = this;
+ return new Stream( f( this.head() ), function () {
+ return self.tail().map( f );
+ } );
+ },
+ reduce: function ( aggregator, initial ) {
+ // requires finite stream
+ if ( this.empty() ) {
+ return initial;
+ }
+ // TODO: iterate
+ return this.tail().reduce( aggregator, aggregator( initial, this.head() ) );
+ },
+ sum: function () {
+ // requires finite stream
+ return this.reduce( function ( a, b ) {
+ return a + b;
+ }, 0 );
+ },
+ walk: function( f ) {
+ // requires finite stream
+ this.map( function ( x ) {
+ f( x );
+ return x;
+ } ).force();
+ },
+ force: function() {
+ // requires finite stream
+ var stream = this;
+ while ( !stream.empty() ) {
+ stream = stream.tail();
+ }
+ },
+ scale: function( factor ) {
+ return this.map( function ( x ) {
+ return factor * x;
+ } );
+ },
+ filter: function( f ) {
+ if ( this.empty() ) {
+ return this;
+ }
+ var h = this.head();
+ var t = this.tail();
+ if ( f( h ) ) {
+ return new Stream( h, function () {
+ return t.filter( f );
+ } );
+ }
+ return t.filter( f );
+ },
+ take: function ( howmany ) {
+ if ( this.empty() ) {
+ return this;
+ }
+ if ( howmany == 0 ) {
+ return new Stream();
+ }
+ var self = this;
+ return new Stream(
+ this.head(),
+ function () {
+ return self.tail().take( howmany - 1 );
+ }
+ );
+ },
+ drop: function( n ){
+ var self = this;
+
+ while ( n-- > 0 ) {
+
+ if ( self.empty() ) {
+ return new Stream();
+ }
+
+ self = self.tail();
+ }
+
+ // create clone/a contructor which accepts a stream?
+ return new Stream( self.headValue, self.tailPromise );
+ },
+ member: function( x ){
+ var self = this;
+
+ while( !self.empty() ) {
+ if ( self.head() == x ) {
+ return true;
+ }
+
+ self = self.tail();
+ }
+
+ return false;
+ },
+ print: function( n ) {
+ var target;
+ if ( typeof n != 'undefined' ) {
+ target = this.take( n );
+ }
+ else {
+ // requires finite stream
+ target = this;
+ }
+ target.walk( function ( x ) {
+ console.log( x );
+ } );
+ },
+ toString: function() {
+ // requires finite stream
+ return '[stream head: ' + this.head() + '; tail: ' + this.tail() + ']';
+ }
+};
+
+Stream.makeOnes = function() {
+ return new Stream( 1, Stream.makeOnes );
+};
+Stream.makeNaturalNumbers = function() {
+ return new Stream( 1, function () {
+ return Stream.makeNaturalNumbers().add( Stream.makeOnes() );
+ } );
+};
+Stream.make = function( /* arguments */ ) {
+ if ( arguments.length == 0 ) {
+ return new Stream();
+ }
+ var restArguments = Array.prototype.slice.call( arguments, 1 );
+ return new Stream( arguments[ 0 ], function () {
+ return Stream.make.apply( null, restArguments );
+ } );
+};
+Stream.range = function ( low, high ) {
+ if ( typeof low == 'undefined' ) {
+ low = 1;
+ }
+ if ( low == high ) {
+ return Stream.make( low );
+ }
+ // if high is undefined, there won't be an upper bound
+ return new Stream( low, function () {
+ return Stream.range( low + 1, high );
+ } );
+};
+
+module.exports = Stream;
View
26 package.json
@@ -0,0 +1,26 @@
+{
+ "name": "Streams",
+ "version": "1.0",
+ "description": "Lazy streams.",
+ "maintainers": [{
+ "name": "Dionysis Zindros",
+ "email": "dionyziz@gmail.com",
+ "web": "http://dionyziz.com/"
+ }],
+ "contributors": [{
+ "name": "Ryan Scheel",
+ "email": "ryan.havvy@gmail.com",
+ "web": "http://widget.mibbit.com/?channel=%23havvy&server=irc.mibbit.net"
+ },
+ {
+ "name": "Dionysis Zindros",
+ "email": "dionyziz@gmail.com",
+ "web": "http://dionyziz.com/"
+ }],
+ "main": "lib",
+ "directories": ["lib"],
+ "engines": {
+ "node": ">=0.4.0"
+ },
+ "keywords": ["stream", "lazy", "infinite", "data structure"]
+}
View
133 spec/stream.spec.js
@@ -0,0 +1,133 @@
+var Stream = require('../lib/index.js');
+
+describe('finite streams', function () {
+ it('can be made and accessess', function () {
+ var finite = Stream.make(10, 20, 30);
+ expect(finite.length()).toBe(3);
+ expect(finite.head()).toBe(10);
+ expect(finite.item(0)).toBe(10);
+ expect(finite.item(1)).toBe(20);
+ expect(finite.item(2)).toBe(30);
+ });
+
+ it('has a head and tail', function () {
+ var ini = {};
+ var fin = {};
+ var duo = Stream.make(ini, fin);
+
+ expect(duo.head()).toBe(ini);
+ expect(duo.tail().head()).toBe(fin);
+ expect(duo.tail().tail().empty()).toBeTruthy();
+ });
+
+ it('detects membership', function () {
+ var stooges = Stream.make("Curly", "Moe", "Larry");
+ expect(stooges.member("Curly")).toBeTruthy();
+ expect(stooges.member("Bobert")).toBeFalsy();
+ });
+});
+
+describe('range', function () {
+ it('can make ranges from a minimum value to a maximal', function () {
+ var three_to_seven = Stream.range(3, 7);
+
+ expect(three_to_seven.length()).toBe(5);
+ expect(three_to_seven.item(0)).toBe(3);
+ expect(three_to_seven.item(1)).toBe(4);
+ expect(three_to_seven.item(2)).toBe(5);
+ expect(three_to_seven.item(3)).toBe(6);
+ expect(three_to_seven.item(4)).toBe(7);
+ });
+
+ it('has an optional highest value', function () {
+ var ten_plus = Stream.range(10);
+ expect(ten_plus.head()).toBe(10);
+ expect(ten_plus.tail().head()).toBe(11);
+ });
+
+ it('defaults to the natural numbers', function () {
+ var naturals = Stream.range();
+ expect(naturals.head()).toBe(1);
+ expect(naturals.tail().head()).toBe(2);
+ });
+});
+
+describe('standard functional functions', function () {
+ it('takes', function () {
+ var naturals = Stream.range();
+ var first_three_naturals = naturals.take(3);
+
+ expect(first_three_naturals instanceof Stream).toBeTruthy();
+ expect(first_three_naturals.length()).toBe(3);
+ expect(first_three_naturals.item(0)).toBe(1);
+ expect(first_three_naturals.item(1)).toBe(2);
+ expect(first_three_naturals.item(2)).toBe(3);
+ expect(function () {first_three_naturals.item(3);})
+ .toThrow('Item index does not exist in stream.')
+ });
+
+ it('drops', function () {
+ oldComment = {};
+ oldestComment = {};
+ newComment = {};
+ newestComment = {};
+ var comments = Stream.make(oldestComment, oldComment, newComment, newestComment);
+ var newComments = comments.drop(2);
+ expect(newComments.length()).toBe(2);
+ expect(newComments.member(oldComment)).toBeFalsy();
+ expect(newComments.member(newComment)).toBeTruthy();
+ expect(newComments.head()).toBe(newComment);
+ expect(newComments.tail().head()).toBe(newestComment);
+ })
+
+ it('maps', function () {
+ var alphabet_ascii = Stream.range(97, 122);
+ var alphabet = alphabet_ascii.map(function (code) {
+ return String.fromCharCode(code);
+ });
+
+ expect(alphabet.head()).toBe('a');
+ expect(alphabet.tail().head()).toBe('b');
+ expect(alphabet.item(25)).toBe('z');
+ });
+
+ it('filters', function () {
+ var first_ten_naturals = Stream.range(1, 10);
+ var first_five_evens = first_ten_naturals.filter(function (n) {
+ return (n % 2 === 0);
+ });
+
+ expect(first_five_evens.length()).toBe(5);
+ first_five_evens.map(function (n) {
+ expect(n / 2).toBe(Math.floor(n / 2));
+ });
+ });
+
+ it('reduces', function () {
+ var first_twenty_naturals = Stream.range(1, 20);
+ var twentieth_triangular_number = first_twenty_naturals.reduce(function (prior, current) {
+ return prior + current;
+ }, 0);
+
+ expect(twentieth_triangular_number).toBe(210);
+ });
+});
+
+describe('special numeric stream functions', function () {
+ it('sums', function () {
+ var first_twenty_naturals = Stream.range(1, 20);
+ var twentieth_triangular_number = first_twenty_naturals.sum();
+ expect(twentieth_triangular_number).toBe(210);
+ });
+
+ it('scales', function () {
+ var first_ten_naturals = Stream.range(1, 10);
+ var first_ten_evens = first_ten_naturals.scale(2);
+
+ expect(first_ten_evens.length()).toBe(10);
+ expect(first_ten_evens.head()).toBe(2);
+ expect(first_ten_evens.item(9)).toBe(20);
+ })
+});
+
+
Something went wrong with that request. Please try again.