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]