JS.Enumerable

JS.Enumerable is essentially a straight port of Ruby’s Enumerable module to JavaScript. Some of the methods have slightly different names in keeping with JavaScript conventions, but the underlying idea is this: the module provides methods usable by any class that represents collections or lists of things. The only stipulation is that your class must have a forEach method that calls a given function with each member of the collection in turn, with an index variable if desired.

Here’s a simple class that stores some of its instance data in a list, just so you get the idea:

        var Collection = new JS.Class({
          include: JS.Enumerable,
        
          initialize: function() {
            this.list = [];
            for (var i = 0, n = arguments.length; i < n; i++)
              this.list.push(arguments[i]);
          },
        
          forEach: function(block, context) {
            for (var i = 0, n = this.list.length; i < n; i++)
              block.call(context || null, this.list[i], i);
          }
        });

Let’s create an instance and see what it does:

        var list = new Collection(3,7,4,8,2);
        list.forEach(function(x, i) {
          console.log(x, i);
        });
        
        // prints...
        //    3 0
        //    7 1
        //    4 2
        //    8 3
        //    2 4

The API provided by the Enumerable module to the Collection class is as follows. In the argument list of each method, block is a function and context is an optional argument that sets the meaning of the keyword this inside block.

all(block, context)

Returns true iff block returns true for every member of the collection. Aliased as every().

        new Collection(3,7,4,8,2).all(function(x) {
          return x > 5;
        })
        // -> false
        
        new Collection(3,7,4,8,2).all(function(x) {
          return typeof x == 'number';
        })
        // -> true

any(block, context)

Returns true if block returns true for one or more members of the collection. Aliased as some().

        new Collection(3,7,4,8,2).any(function(x) {
          return x > 5;
        })
        // -> true
        
        new Collection(3,7,4,8,2).any(function(x) {
          return typeof x == 'object';
        })
        // -> false

collect(block, context)

Alias for map().

detect(block, context)

Alias for find().

entries()

Alias for toArray().

every(block, context)

Alias for all().

forEachCons(n, block, context)

Calls block with every set of n consecutive members of the collection.

        new Collection(3,7,4,8,2).forEachCons(3, function(list) {
          console.log(list);
        });
        
        // prints
        //    [3, 7, 4]
        //    [7, 4, 8]
        //    [4, 8, 2]

forEachSlice(n, block, context)

Splits the collection up into pieces of length n, and call block with each piece in turn.

        new Collection(3,7,4,8,2).forEachSlice(2, function(list) {
          console.log(list);
        });
        
        // prints
        //    [3, 7]
        //    [4, 8]
        //    [2]

filter(block, context)

Alias for select().

find(block, context)

Returns the first member of the collection for which block returns true. Aliased as detect().

        new Collection(3,7,4,8,2).find(function(x) {
          return x > 5;
        })
        // -> 7

findAll(block, context)

Alias for select().

inject(memo, block, context)

Returns the result of reducing the collection down to a single value using a callback function. The first time your block is called, it is passed the value of memo you specified. The return value of block becomes the next value of memo.

        // sum the values
        new Collection(3,7,4,8,2).inject(0, function(memo, x) {
          return memo + x;
        })
        // -> 24

map(block, context)

Returns an Array formed by calling block on each member of the collection. Aliased as collect().

        // square the numbers
        new Collection(3,7,4,8,2).map(function(x) {
          return x * x;
        })
        // -> [9, 49, 16, 64, 4]

max(block, context)

Returns the member of the collection with the maximum value. Members must use Comparable or be comparable using JavaScript’s standard comparison operators. If a block is passed, it is used to sort the members. If no block is passed, a sensible default sort method is used.

        new Collection(3,7,4,8,2).max()   // -> 8
        
        new Collection(3,7,4,8,2).max(function(a,b) {
          return (a%7) - (b%7);
        })
        // -> 4

member(needle)

Returns true iff the collection contains any members equal to needle. Items are checked for equality (==) rather than identity (===).

        new Collection(3,7,4,8,2).member('7')   // -> true
        new Collection(3,7,4,8,2).member('foo') // -> false

min(block, context)

Much like max(), except it returns the minimum value.

partition(block, context)

Returns two arrays, one containing members for which block returns true, the other containing those for which it returns false.

        new Collection(3,7,4,8,2).partition(function(x, i) {
          return x > i;
        })
        // -> [ [3, 7, 4, 8], [2] ]

select(block, context)

Returns a new Array containing the members of the collection for which block returns true. Aliased as filter() and findAll().

        new Collection(3,7,4,8,2).select(function(x) {
          return x > 5;
        })
        // -> [7, 8]

reject(block, context)

Returns a new Array containing the members of the collection for which block returns false.

        new Collection(3,7,4,8,2).reject(function(x) {
          return x > 5;
        })
        // -> [3, 4, 2]

some(block, context)

Alias for any().

sort(block, context)

Returns a new Array containing the members of the collection in sort order. The members must either use Comparable or be comparable using JavaScript’s standard comparison operators. If no block is passed, a sensible default sort method is used, otherwise the block itself is used to perform sorting.

        new Collection(3,7,4,8,2).sort()
        // -> [2, 3, 4, 7, 8]
        
        // sort by comparing values modulo 7
        new Collection(3,7,4,8,2).sort(function(a,b) {
          return (a%7) - (b%7);
        })
        // -> [7, 8, 2, 3, 4]

sortBy(block, context)

Returns a new Array containing the members of the collection sorted according to the value that block returns for them.

        // sort values modulo 7
        new Collection(3,7,4,8,2).sortBy(function(x) {
          return x % 7;
        })
        // -> [7, 8, 2, 3, 4]

toArray()

Returns a new Array containing the members of the collection. Aliased as entries().

zip(args, block, context)

This one is rather tricky to explain in words, so I’ll just let the Ruby docs explain:

Converts any arguments to arrays, then merges elements of collection with corresponding elements from each argument. This generates a sequence of n-element arrays, where n is one more that the count of arguments. If the size of any argument is less than the size of the collection, null values are supplied. If a block is given, it is invoked for each output array, otherwise an array of arrays is returned.

What this translates to in practise:

        new Collection(3,7,4,8,2).zip([1,9,3,6,4], [6,3,3])
        
        // -> [ [3,1,6], [7,9,3], [4,3,3],
        //        [8,6,null], [2,4,null] ]
        
        new Collection(3,7,4,8,2).zip([1,9,3,6,4], function(list) {
          console.log(list)
        })
        
        // prints...
        //    [3, 1]
        //    [7, 9]
        //    [4, 3]
        //    [8, 6]
        //    [2, 4]