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]