Created
December 27, 2011 22:28
-
-
Save poetix/1525363 to your computer and use it in GitHub Desktop.
Lazy map, filter and reduce in Javascript
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Iterators = (function() { | |
var each, map, filter, reduce, toArray, toObject; | |
function _map(iter, f) { | |
return { | |
hasNext: iter.hasNext, | |
next: function() { return f(iter.next()); } | |
}; | |
} | |
function _each(iter, f) { | |
while (iter.hasNext()) { | |
f(iter.next()); | |
} | |
} | |
function _toArray(iter) { | |
var array = []; | |
_each(iter, function(item) { array.push(item); }); | |
return array; | |
} | |
function _toObject(iter) { | |
var object = {}; | |
_each(iter, function(item) { | |
object[item.key] = item.value; | |
}); | |
return object; | |
} | |
function peekable(iter) { | |
var buffer = undefined; | |
if (iter.peek) { return iter; } | |
return { | |
hasNext: function() { | |
if (buffer === undefined) { | |
return iter.hasNext(); | |
} | |
return true; | |
}, | |
next: function() { | |
var result; | |
if (buffer === undefined) { | |
result = iter.next(); | |
} else { | |
result = buffer; | |
} | |
buffer = undefined; | |
return result; | |
}, | |
peek: function() { | |
if (buffer === undefined) { | |
buffer = iter.next(); | |
} | |
return buffer; | |
} | |
}; | |
} | |
function _filter(iter, p) { | |
var peekableIter = peekable(iter); | |
function hasNext() { | |
while(peekableIter.hasNext()) { | |
if (p(peekableIter.peek())) { | |
return true; | |
} | |
peekableIter.next(); | |
} | |
} | |
return { | |
hasNext: hasNext, | |
next: function() { | |
if (hasNext()) { | |
return peekableIter.next(); | |
} | |
} | |
}; | |
} | |
function _reduce(iter, f, seed) { | |
var result = seed === undefined ? iter.next() : seed; | |
while (iter.hasNext()) { | |
result = f(result, iter.next()); | |
} | |
return result; | |
} | |
function arrayIterator(array) { | |
var i = 0; | |
return { | |
hasNext: function() { return i < array.length; }, | |
next: function() { return array[i++]; } | |
}; | |
} | |
function keyIterator(object) { | |
var key = null, keys = []; | |
for (key in object) { | |
if (object.hasOwnProperty(key)) { keys.push(key); } | |
} | |
return arrayIterator(keys); | |
} | |
function valueIterator(object) { | |
return _map(keyIterator(object), function(key) { return object[key]; }); | |
} | |
function objectIterator(object) { | |
return _map(keyIterator(object), function(key) { | |
return { key: key, value: object[key] }; | |
}); | |
} | |
function toIterator(arg) { | |
if (arg.hasNext && arg.next) { return arg; } | |
if (arg instanceof Array) { | |
return arrayIterator(arg); | |
} | |
if (typeof arg === 'object') { | |
return objectIterator(arg); | |
} | |
throw "Cannot convert argument to iterator: " + arg; | |
} | |
function convertArgToIterator(f) { | |
return function() { | |
var args = arguments; | |
args[0] = toIterator(args[0]); | |
return f.apply(f, args); | |
}; | |
} | |
each = convertArgToIterator(_each); | |
map = convertArgToIterator(_map); | |
filter = convertArgToIterator(_filter); | |
reduce = convertArgToIterator(_reduce); | |
toArray = convertArgToIterator(_toArray); | |
toObject = convertArgToIterator(_toObject); | |
return { | |
each: each, | |
map: map, | |
filter: filter, | |
reduce: reduce, | |
keyIterator: keyIterator, | |
valueIterator: valueIterator, | |
toIterator: toIterator, | |
toArray: toArray, | |
toObject: toObject, | |
project: function(value, propertyName) { | |
return map(value, function(item) { return item[propertyName]; }); | |
} | |
}; | |
}()); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var _ = Iterators; | |
describe("Iterators.keyIterator", function() { | |
it("should return an iterator over an object's keys", function() { | |
var input = { foo: "bar", baz: "xyzzy" }, | |
iter = _.keyIterator(input); | |
expect(iter.next()).toEqual("foo"); | |
expect(iter.next()).toEqual("baz"); | |
}); | |
}); | |
describe("Iterators.valueIterator", function() { | |
it("should return an iterator over an object's values", function() { | |
var input = { foo: "bar", baz: "xyzzy" }, | |
iter = _.valueIterator(input); | |
expect(iter.next()).toEqual("bar"); | |
expect(iter.next()).toEqual("xyzzy"); | |
}); | |
}); | |
describe("Iterators.toIterator", function() { | |
it("should return an iterator verbatim", function() { | |
var iter = _.toIterator([1, 2, 3]); | |
expect(_.toIterator(iter)).toEqual(iter); | |
}); | |
it("should convert an array to an array iterator", function() { | |
var input = [1, 2, 3], | |
iter = _.toIterator(input); | |
expect(iter.next()).toEqual(1); | |
expect(iter.next()).toEqual(2); | |
expect(iter.next()).toEqual(3); | |
}); | |
it("should convert an object to an entry iterator", function() { | |
var input = { foo: "bar", baz: "xyzzy" }, | |
iter = _.toIterator(input); | |
expect(iter.next()).toEqual({key: "foo", value: "bar"}); | |
expect(iter.next()).toEqual({key: "baz", value: "xyzzy"}); | |
}); | |
}); | |
describe("Iterators.toArray", function() { | |
it("should convert an iterator into an array", function() { | |
var input = [1, 2, 3], | |
iter = _.toIterator(input); | |
expect(_.toArray(iter)).toEqual(input); | |
}); | |
}); | |
describe("Iterators.map", function() { | |
it("should return a lazy iterator", function() { | |
var input = [1, 2, 3], | |
f = jasmine.createSpy('map function'), | |
iter = _.map(input, f); | |
expect(f).wasNotCalled(); | |
iter.next(); | |
expect(f).toHaveBeenCalledWith(1); | |
}); | |
it("should apply the supplied function to each element in the iterator", function() { | |
var input = [1, 2, 3]; | |
expect(_.toArray(_.map(input, function(n) { return n * 2; }))).toEqual([2, 4, 6]); | |
}); | |
}); | |
describe("Iterators.project", function() { | |
it("should perform a map projecting the supplied property", function() { | |
var input = [{x: 1, y: 10}, | |
{x: 2, y: 20}]; | |
expect(_.toArray(_.project(input, "x"))).toEqual([1, 2]); | |
expect(_.toArray(_.project(input, "y"))).toEqual([10, 20]); | |
}); | |
}); | |
describe("Iterators.filter", function() { | |
it("should return a lazy iterator", function() { | |
var input = [1, 2, 3], | |
p = jasmine.createSpy('predicate'), | |
iter = _.filter(input, p); | |
expect(p).wasNotCalled(); | |
iter.hasNext(); | |
expect(p).toHaveBeenCalledWith(1); | |
}); | |
it("should return an iterator over the elements matched by the supplied predicate", function() { | |
var input = [1, 2, 3, 4, 5, 6], | |
isEven = function(number) { return ((number >> 1) << 1) === number; }; | |
expect(_.toArray(_.filter(input, isEven))).toEqual([2, 4, 6]); | |
}); | |
}); | |
describe("Iterators.toObject", function() { | |
it("should convert an entry iterator into an object", function() { | |
var input = { foo: "bar", baz: "xyzzy" }, | |
iter = _.toIterator(input); | |
expect(_.toObject(iter)).toEqual(input); | |
}); | |
}); | |
describe("Iterators.reduce", function() { | |
it("should apply the reduction using the first element of the iterator as seed value " + | |
"if none is supplied", function() { | |
var input = [1, 2, 3], | |
sum = function(a, b) { return a + b; }; | |
expect(_.reduce(input, sum)).toEqual(6); | |
}); | |
it("should use the given seed value if it is supplied", function() { | |
var input = [1, 2, 3], | |
sum = function(a, b) { return a + b; }; | |
expect(_.reduce(input, sum, 4)).toEqual(10); | |
}); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment