Enumerable

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. The forEach method should return an Enumerator if called without an iterator function – see the example below.

// In the browser
JS.require('JS.Enumerable', function(Enumerable) { ... });

// In CommonJS
var Enumerable = require('jsclass/src/enumerable').Enumerable;

As a basic example, here’s a simple class that stores some of its instance data in a list. A class may store collections in any way it chooses, and does not necessarily have to guarantee any particular iteration order; the purpose of the forEach method is to hide the storage mechanism from the users of the class.

var Collection = new Class({
    include: Enumerable,

    initialize: function() {
        this._list = [];
        for (var i = 0, n = arguments.length; i < n; i++)
            this._list.push(arguments[i]);
    },

    forEach: function(block, context) {
        if (!block) return this.enumFor('forEach');

        for (var i = 0, n = this._list.length; i < n; i++)
            block.call(context, this._list[i]);

        return this;
    }
});

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

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

// prints:
//    3,  7,  4,  8,  2

The API provided by the Enumerable module to the Collection class is listed below. 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. For many methods, block may be a String, emulating Ruby’s Symbol#to_proc functionality. Some examples:

var strings = new Collection('iguana', 'labrador', 'albatross');

strings.map('length')
// -> [6, 8, 9]

strings.map('toUpperCase')
// -> ["IGUANA", "LABRADOR", "ALBATROSS"]

The string may refer to a method or a property of the objects in the collection, and is converted to a function that calls the named method on the first argument, passing the remaining arguments as parameters to the call. In other words:

strings.map('toUpperCase')
// is converted to:

strings.map(function(a, b, ...) { return a.toUpperCase(b, ...) })

Most of JavaScript’s binary operators are supported, so you can use them with between items in inject loops, for example:

new Collection(1,2,3,4).inject('+')
// -> 10

var tree = {A: {B: {C: 87}}};
new Collection('A','B','C').inject(tree, '[]')
// -> 87

all(block, context)

Returns true iff block returns true for every member of the collection. If called without a block, returns true iff all the members of the collection have truthy values. Aliased as every().

var list = new Collection(3,7,4,8,2);

list.all(function(x) { return x > 5 });
// -> false

list.all(function(x) { return typeof x == 'number' });
// -> true

new Collection(3,0,5).all();
// -> false

any(block, context)

Returns true iff block returns true for one or more members of the collection. If called without a block, returns true iff one or more members has a truthy value. Aliased as some().

var list = new Collection(3,7,4,8,2);

list.any(function(x) { return x > 5 });
// -> true

list.any(function(x) { return typeof x == 'object' });
// -> false

list.any();
// -> true

new Collection(0, false, null).any();
// -> false

chunk(block, context)

Splits the collection into groups of adjacent elements that return the same value for the block, and returns an array whose elements contain the current block value and the string of items that returned that value.

var list = new Collection([3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5]);
list.chunk(function(value) { return value % 2 === 0 })

// -> [
//        [false, [3, 1]],
//        [true,  [4]],
//        [false, [1, 5, 9]],
//        [true,  [2, 6]],
//        [false, [5, 3, 5]]
//    ]

collect(block, context)

Alias for map().

count(needle, context)

If called without arguments, returns the number of items in the collection. If called with arguments, returns the number of members that are equal to needle using equality semantics, or for which needle returns true if needle is a function.

new Collection(3,7,4,8,2).count();
// -> 5

new Collection(3,7,4,8,2).count(2);
// -> 1

new Collection(3,7,4,8,2).count(function(x) { return x % 2 == 0 });
// -> 3

cycle(n, block, context)

Loops over the collection n times, calling block with each member. Equivalent to calling forEach(block, context) n times.

detect(block, context)

Alias for find().

drop(n)

Returns a new Array containing all but the first n members of the collection.

dropWhile(block, context)

Returns the collection as a new Array, removing items from the front of the list up to but not including the first item for which block returns false.

entries()

Alias for toArray().

every(block, context)

Alias for all().

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().

findIndex(needle, context)

Returns the index of the first member of the collection equal to needle, or for which needle returns true if needle is a function. Returns null if no match is found.

first(n)

Returns an Array containing the first n members, or returns just the first member if n is not specified.

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]

forEachWithIndex(block, context)

Calls the block with each member of the collection in turn, passing the member and its index to the block.

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

forEachWithObject(object, block, context)

Calls block with each member of the collection, passing object and the current member with each call, and returns the current object.

var list = new Collection(3,7,4,8,2);

list.forEachWithObject([], function(ary, item) {
    ary.unshift(item * item);
});
// -> [4, 64, 16, 49, 9]

grep(pattern, block, context)

Returns an Array of all the members of the collection that match pattern according to the method pattern.match(). pattern may be a RegExp, a Module, Class, Range, or any other object with a match() method that returns true or false. If block is given, each match is transformed by passing it to block.

var strings = new Collection('iguana', 'labrador', 'albatross');

strings.grep(/[aeiou]a/);
// -> ["iguana"]

strings.grep(/[aeiou]a/, function(s) { return s.toUpperCase() });
// -> ["IGUANA"]

groupBy(block, context)

Groups the members according to their return value when passed to block, and returns a Hash, where in each pair the key is a return value for block and the value is an Array of items that produced that value.

var list   = new Collection(1,2,3,4,5,6);
var groups = list.groupBy(function(x) { return x % 3 });

groups.keys()   // -> [1, 2, 0]
groups.get(1)   // -> [1, 4]
groups.get(2)   // -> [2, 5]
groups.get(0)   // -> [3, 6]

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.

var list = new Collection(3,7,4,8,2);

list.max()   // -> 8

list.max(function(a,b) { return (a%7) - (b%7) });
// -> 4

maxBy(block, context)

Returns the member of the collection that gives the maximum value when passed to block.

member(needle)

Returns true iff the collection contains any members equal to needle. Items are checked for identity (===), or using the equals() method if the objects implement it.

var list = new Collection(3,7,4,8,2);
list.member('7')   // -> false
list.member(7)     // -> true

min(block, context)

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

minBy(block, context)

Much like maxBy(), except it returns the member that gives the minimum value.

minmax(block, context)

Returns the array [min(block, context), max(block, context)].

minmaxBy(block, context)

Returns the array [minBy(block, context), maxBy(block, context)].

none(block, context)

Returns !collection.any(block, context).

one(block, context)

Returns true iff block returns true for exactly one member of the collection. If block is not given, returns true iff exactly one member has a truthy 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) { return x > 5 });
// -> [ [7, 8], [3, 4, 2] ]

reverseForEach(block, context)

Calls block with each member of the collection, in the opposite order given by forEach().

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]

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]

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.

var list = new Collection(3,7,4,8,2);

list.sort()
// -> [2, 3, 4, 7, 8]

// sort by comparing values modulo 7
list.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]

take(n)

Returns the first n members from the collection.

takeWhile(block, context)

Returns items from the start of the collection, up to but not including the first item for which block returns false.

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]