Skip to content

Instantly share code, notes, and snippets.

@archanpatkar
Created July 16, 2017 21:37
Show Gist options
  • Save archanpatkar/c4456e05e25d965705d198853404e093 to your computer and use it in GitHub Desktop.
Save archanpatkar/c4456e05e25d965705d198853404e093 to your computer and use it in GitHub Desktop.
Using ES6 Generator prototype model to implement lazy chaining. When you set the prototype of multiple Generator Functions to a common prototype you can chain them. Examples at the bottom. (this code currently only works in Firefox Aurora, but will eventually work in all JS engines).
const module = { exports: {} };
let exports = module.exports;
const GeneratorFunction = function*(){}.constructor;
const iteratorSymbol = (typeof Symbol === "function" && Symbol.iterator) || "@@iterator";
const MISSING = {};
/**
* A generator function is always called as a constructor. If multiple
* generators share the same prototype, and you put generators on that shared
* prototype as methods, you can do chaining.
*/
function* Chainable(iterable) {
if (iterable && typeof iterable[iteratorSymbol] === "function") {
yield* iterable;
}
}
module.exports = exports = Chainable;
// Generators don't set their "constructor" property like normal functions do.
Object.defineProperty(Chainable.prototype, "constructor", {
configurable: true,
writable: true,
value: Chainable
});
/**
* Add a helper function.
* @param Function f
* The helper function.
* @return Function
* The helper function.
*/
function helper(f) {
if (f instanceof GeneratorFunction) {
f.prototype = Chainable.prototype;
}
return f;
}
/**
* Export a function and set it to use the shared prototype.
*/
function exportFunction(f) {
helper(f);
return exports[f.name] = f;
}
/**
* Export a function and also add a method version of it to the shared
* prototype. This allows it to be used to chain from other generators produced
* by the generator functions in this module.
*
* @see exportFunction
*/
function exportMethod(f) {
exportFunction(f);
Chainable.prototype[f.name] = function(...args) {
return f(this, ...args);
};
}
/**
* Get an iterator from an iterable.
* @param Iterable iterable
* The iterable to get the iterator from.
* @return Iterator
* The iterator for the iterable.
*/
const iter = exportFunction(function iter(iterable) {
if (iterable && typeof iterable[iteratorSymbol] === "function") {
return iterable[iteratorSymbol]();
}
throw new TypeError("target is not iterable");
});
/**
* An infinite generator that counts up.
*
* @param Number [start]
* The number to start from.
* @param Number [step]
* The amount to change each iteration.
* @return Chainable
*/
exportFunction(function* count(start = 0, step = 1) {
for (;;) {
yield start;
start += step;
}
});
/**
* A generator that generates numbers in a range using a given step. Can be
* called with parameters [stop] or [start, stop] or [start, stop, step].
*
* @param Number [start]
* The number to start from.
* @param Number stop
* The amount to stop at.
* @param Number [step]
* The amount to change each iteration.
* @return Chainable
*/
exportFunction(function* range(start = 0, stop, step = 1) {
if (arguments.length < 2) {
stop = start;
start = 0;
}
const count = Math.max(Math.ceil((stop - start) / step), 0);
for (let i = 0; i < count; i++) {
yield start;
start += step;
}
});
/**
* A generator that repeatedly yields a value. By default this is an infinite
* generator.
*
* @param Any value
* The value to yield.
* @param Number [count]
* The amount of times to repeat the value.
* @return Chainable
*/
exportFunction(function* repeat(value, count = Infinity) {
while (count-- > 0) {
yield value;
}
});
/**
* A generator that yield the for..in keys for an object.
* @param Object object
* The object to get the keys for.
* @return Array
* The array of keys.
*/
function* enumerate(object) {
for (let key in object) {
yield key;
}
}
/**
* A generator that yields keys for an object's own properties.
*
* @param Object object
* The object to yield keys from.
* @param Boolean [ownProperties]f
* Whether to include only own properties or also prototype properties.
* @return Chainable
*/
exportFunction(function* keys(object, ownProperties = true) {
const getKeys = ownProperties ? Object.keys : enumerate;
yield* getKeys(object);
});
/**
* A generator that yields the values for an object's own properties.
*
* @param Object object
* The object to yield values from.
* @param Boolean [ownProperties]
* Whether to include only own properties or also prototype properties.
* @return Chainable
*/
exportFunction(function* values(object, ownProperties = true) {
const getKeys = ownProperties ? Object.keys : enumerate;
for (let key of getKeys(object)) {
yield object[key];
}
});
/**
* A generator that yields [key, value] pairs for an object's own properties.
*
* @param Object object
* The object to yield [key, value] pairs from.
* @param Boolean [ownProperties]
* Whether to include only own properties or also prototype properties.
* @return Chainable
*/
exportFunction(function* entries(object, ownProperties = true) {
const getKeys = ownProperties ? Object.keys : enumerate;
for (let key of getKeys(object)) {
yield [key, object[key]];
}
});
/**
* Repeatedly execs a RegExp against a string, yielding the captures each time.
*
* @param RegExp regexp
* The RegExp to exec.
* @param String string
* @return Chainable
*/
exportFunction(function* exec(regexp, string) {
for (let match; match = regexp.exec(string); yield match);
});
/**
* A generator that creates a subproduct from a provided iterable and another
* product to chain from.
*
* @param Iterable chained
* The Iterable to chain from.
* @param Iterable iterable
* The Iterable to add values from.
* @return Chainable
*/
const _product = helper(function*(chained, iterable) {
for (let tuple of chained) {
for (let item of iterable) {
yield tuple.concat(item);
}
}
});
/**
* A generator to create the cartesian product of the provided iterables.
*
* @param Iterable... iterables
* The Iterables to create the product of.
* @return Chainable
*/
exportMethod(function product(...iterables) {
return iterables.reduce(_product, iter([[]]));
});
/**
* A generator that doesn't yield values until a given callback returns truthy.
* After that it yields all of the remaining values.
*
* @param Iterable iterable
* The iterable to drop values from.
* @param Function callback
* The callback to test each value.
* @param Any [thisArg]
* Optional |this| value the callback will be called with.
* @return Chainable
*/
exportMethod(function* dropWhile(iterable, callback, thisArg) {
for (let item of iterable) {
if (!callback.call(thisArg, item)) {
break;
}
}
yield* iterable;
});
/**
* A generator that yields values until a given callback returns falsey.
*
* @param Iterable iterable
* The iterable to take values from.
* @param Function callback
* The callback to test each value.
* @param Any [thisArg]
* Optional |this| value the callback will be called with.
* @return Chainable
*/
exportMethod(function* takeWhile(iterable, callback, thisArg) {
for (let item of iterable) {
if (!callback.call(thisArg, item)) {
return;
}
yield item;
}
});
/**
* A generator that skips a given amount of items and stops at a given end.
*
* @param Iterable iterable
* The iterable to take values from.
* @param Number [start]
* How many items to skip at the beginning.
* @param Number end
* The total amount of items after which to end at.
* @return Chainable
*/
exportMethod(function* slice(iterable, start, end) {
if (arguments.length < 3) {
end = start;
start = 0;
}
if (end <= start) {
return;
}
let index = 0;
for (let item of iterable) {
if (index >= start) {
yield item;
}
if (index++ === end) {
return;
}
}
});
/**
* A generator that yields values that return truthy from a given callback.
*
* @param Iterable iterable
* The iterable to filter values from.
* @param Function callback
* The callback to test each value.
* @param Any [thisArg]
* Optional |this| value the callback will be called with.
* @return Chainable
*/
exportMethod(function* filter(iterable, callback, thisArg) {
let index = 0;
for (let item of iterable) {
if (callback.call(thisArg, item, index++, iterable)) {
yield item;
}
}
});
/**
* A generator that produces filters it so each value is only seen once.
*
* @param Iterable iterable
* The iterables to filter by uniques.
* @return Chainable
*/
exportMethod(function* unique(iterable) {
const seen = new Set();
for (let item of iterable) {
if (!seen.has(item)) {
seen.add(item);
yield item;
}
}
seen.clear();
});
/**
* A generator that yields values transformed by a given callback.
*
* @param Iterable iterable
* The iterable to map values from.
* @param Function callback
* The callback to transform each value.
* @param Any [thisArg]
* Optional |this| value the callback will be called with.
* @return Chainable
*/
exportMethod(function* map(iterable, callback, thisArg) {
let index = 0;
for (let item of iterable) {
yield callback.call(thisArg, item, index++, iterable);
}
});
/**
* A generator that yields values from a callback that is called with arguments
* yielded from a given Iterable.
*
* @param Iterable iterable
* The iterable to map values from.
* @param Function callback
* The callback to transform each value.
* @param Any [thisArg]
* Optional |this| value the callback will be called with.
* @return Chainable
*/
exportMethod(function* starMap(iterable, callback, thisArg) {
for (let args of iterable) {
yield callback.call(thisArg, ...args);
}
});
/**
* A generator that calls a given callback for each value, but yields it
* directly through unchanged.
*
* @param Iterable iterable
* The iterable to tap values from.
* @param Function callback
* The callback that each action will be reported to. Called with
* arguments for action and value. Action is one of
* ["next", "throw", "return"].
* @param Any [thisArg]
* Optional |this| value the callback will be called with.
* @return Chainable
*/
exportMethod(function* tap(iterable, callback, thisArg) {
const iterator = iter(iterable);
let action = "next";
let nextValue;
for (;;) {
const {done, value} = iterator[action](sentValue);
if (done) {
callback.call(thisArg, "return", value);
return value;
}
callback.call(thisArg, action, value);
try {
nextValue = yield value;
action = "next";
} catch (e) {
nextValue = e;
action = "throw";
}
}
});
/**
* A generator that wraps a given iterable to handle errors, optionally
* reporting them to a callback.
*
* @param Iterable iterable
* The iterable to tap values from.
* @param Function [callback]
* The callback to any encountered errors will be reported to.
* @return Chainable
*/
exportMethod(function* squelch(iterable, callback = () => {}) {
let iterator;
try {
iterator = iter(iterable);
} catch (e) {
callback(e);
return;
}
let sendValue;
for (;;) {
let done, value;
try {
({done, value}) = iterator.next(sendValue);
if (done) {
return value;
}
} catch (e) {
callback(e);
continue;
}
sendValue = yield value;
}
});
/**
* A generator that yields [index, value] pairs from the values in a given
* iterable.
*
* @param Iterable iterable
* The iterables to yield indexed values from.
* @param Number [start]
* The index to start from. Defaults to 0.
* @param Number [step]
* The amount to increase for each index. Defaults to 1.
* @return Chainable
*/
exportMethod(function* index(iterable, start = 0, step = 1) {
for (let value of iterable) {
yield [start, value];
start += step;
}
});
/**
* A generator that yields a given property from each value from the provided
* iterable.
*
* @param Iterable iterable
* The iterable to pluck properties from.
* @param String key
* The key of the property to pluck from each value.
* @return Chainable
*/
exportMethod(function* pluck(iterable, key) {
for (let item of iterable) {
yield item[key];
}
});
/**
* A generator that yields the resulted of invoking a given method on each
* value from a provided iterable with the given arguments.
*
* @param Iterable iterable
* The iterable to pluck properties from.
* @param String key
* The key of the property to pluck from each value.
* @param Any... args
* Any additional arguments to pass to the method.
* @return Chainable
*/
exportMethod(function* invoke(iterable, key, ...args) {
for (let item of iterable) {
yield item[key](...args);
}
});
/**
* A generator that repeatedly loops through a given iterable's initial value
* set. This is an infinite generator. By default this is an infinite generator.
*
* @param Iterable iterable
* The iterable to map values from.
* @param Number [count]
* The amount of times to cycle the iterable.
* @return Chainable
*/
exportMethod(function* cycle(iterable, count = Infinity) {
const cache = [];
for (let item of iterable) {
yield item;
cache.push(item);
}
if (cache.length) {
for (let i = 0; i < count; i++) {
yield* cache;
}
}
});
/**
* A generator that combines any number of iterables into a single iterable,
* yielding from start to finish.
*
* @param Iterable... iterables
* The iterables to combine into one iterable.
* @return Chainable
*/
exportMethod(function* chain(...iterables) {
for (let iterable of iterables) {
yield* iterable;
}
});
const _zip = helper(function*(iterables, longest) {
if (!iterables.length) {
return;
}
const iterators = iterables.map(iter);
for (;;) {
const values = [];
for (let iterator of iterators) {
const {done, value} = iterator.next();
if (done) {
if (!longest) {
return;
}
values.length++;
} else {
values.push(value);
}
}
yield values;
}
});
/**
* A generator that executes multiple iterators in parallel, yielding an array
* for each set until the first one is exhausted.
*
* @param Iterable... iterables
* The iterables to combine in parallel.
* @return Chainable
*/
exportMethod(function zip(...iterables) {
return _zip(iterables, false);
});
/**
* A generator that executes multiple iterators in parallel, yielding an array
* for each set until the first one is exhausted.
*
* @param Iterable... iterables
* The iterables to combine in parallel.
* @return Chainable
*/
exportMethod(function zipLongest(...iterables) {
return _zip(iterables, true);
});
const QUEUE_COMPACT_SIZE = 500;
/**
* An efficient implementation of the queue data structure.
* @param Iterable [iterable]
* Optional iterable to initialize the values in the Queue.
*/
function Queue(iterable) {
this._head = 0;
this._items = iterable ? [...iterable] : [];
this.size = this._items.length;
}
Queue.prototype.enqueue = function(item) {
this._items.push(item);
return ++this.size;
};
Queue.prototype.dequeue = function() {
if (!this.size) {
return;
}
const item = this._items[this._head];
if (this._head === QUEUE_COMPACT_SIZE) {
this._items = this._items.slice(this._head + 1);
this._head = 0;
} else {
this._items[this._head++] = MISSING;
}
return item;
};
Queue.prototype[iteratorSymbol] = function*() {
if (!this.size) {
return;
}
const {front} = this;
const items = this._items;
let index = this._head;
yield front;
for (; index < items.length; index++) {
const item = items[index];
if (item !== MISSING) {
yield item;
}
}
};
/**
* A generator that works in tandem with multiple other generators to
* individually yield values from a a generator that yields tuples.
*
* @param Iterator iterator
* Source Iterator to draw tuples from.
* @param Number index
* Index that this generator should draw values using from the source
* generator's tuples.
* @param Queue... queues
* Array of Queues for all the generators unzipping from this iterator.
* Indices in this array will match the index in the tuple.
* @return Chainable
*/
const _unzip = helper(function*(iterator, index, queues) {
const queue = queues[index];
for (;;) {
if (queue.size) {
yield queue.dequeue();
} else {
const {value, done} = iterator.next();
if (done) {
return;
}
let item = MISSING;
for (let i = 0; i < queues.length; i++) {
if (i in value) {
if (index === i) {
item = value[i];
} else {
queues[i].enqueue(value[i]);
}
}
}
if (item !== MISSING) {
yield item;
}
}
}
});
/**
* Takes an iterator that yields tuples of values and creates multiple
* generators, each yielding a values from a given index in the yielded tuplees.
*
* @param Iterable iterable
* The iterable to split into multiple iterables.
* @return Chainable...
*/
exportMethod(function unzip(iterable) {
const iterator = iter(iterable);
const {done, value} = iterator.next();
if (done) {
return [];
}
const count = value.length;
const iterables = new Array(count);
const queues = new Array(count);
while (count--) {
queues[count] = new Queue();
queues[count].enqueue(value[count]);
iterables[count] = _unzip(iterator, count, queues);
}
return iterables;
});
/**
* A generator that filters elements from an iterable based on the truthiness
* of the values yielded from a second iterable.
*
* @param Iterable iterable
* The Iterable to filter.
* @param Iterable selectors
* The Iterable who's values' truthiness will be used as a filter.
* @return Chainable
*/
exportMethod(function* compress(iterable, selectors) {
selectors = iter(selectors);
for (let item of iterable) {
if (selectors.next().value) {
yield item;
}
}
});
/**
* Groupby description.
*
* @param Iterable iterable
* The Iterable to filter.
* @param Function [keyCallback]
* The optional callback to provide a key for a given value.
* @return Chainable
*/
exportMethod(function* groupBy(iterable, keyCallback = x => x) {
function* group(targetKey) {
while (currentKey === targetKey) {
yield currentValue;
let {done, value} = iterator.next();
if (done) {
return;
}
currentKey = keyCallback(currentValue = value);
}
}
const iterator = iter(iterable);
let targetKey = MISSING;
let currentKey = MISSING;
let currentValue = MISSING;
for (;;) {
while (currentKey === targetKey) {
let {done, value} = iterator.next();
if (done) {
return;
}
currentKey = keyCallback(currentValue = value);
}
yield [currentKey, group(targetKey = currentKey)];
}
});
/**
* A generator that yields tuples of values yielded from a given iterable.
*
* @param Iterable iterable
* The source Iterable.
* @param Number chunkSize
* The amount of yielded values to collect into each array to be yielded.
* @param Boolean [abbreviate]
* Optionally discard the last tuple if its not full because the Iterable
* ended early.
* @return Chainable
*/
exportMethod(function* chunk(iterable, chunkSize, abbreviate = false) {
let items = [];
let index = 0;
for (let item of iterable) {
items[index++] = item;
if (index === chunkSize) {
yield items;
items = [];
index = 0;
}
}
if (index && (index === chunkSize || !abbreviate)) {
yield items;
}
});
/**
* A generator that caches values and is capable of working in tandem with
* multiple other instances using the same iterator. Used by tee.
*
* @param Iterator iterator
* The source Iterator.
* @param Array cache
* Stores values until all iterators have yielded the value.
* @return Chainable
*/
const _tee = helper(function*(iterator, cache) {
const {items} = cache;
let index = 0;
for (;;) {
if (index === items.length) {
let {done, value} = iterator.next();
if (done) {
if (cache.returned === MISSING) {
cache.returned = value;
}
break;
}
yield items[index++] = value;
} else if (index === cache.tail) {
let value = items[index];
if (index === QUEUE_COMPACT_SIZE) {
items = cache.items = items.slice(index);
index = 0;
cache.tail = 0;
} else {
items[index] = undefined;
cache.tail = ++index;
}
yield value;
} else {
yield items[index++];
}
}
if (cache.tail === index) {
items.length = 0;
}
return cache.returned;
});
/**
* Creates multiple generators that each yield the same output as a single input
* iterable. They can be iterated at different times and speeds and still yield
* the same values in the same order.
*
* @param Iterable iterable
* The Iterable to copy.
* @param Number [count]
* The number of copies to make.
* @return Chainable...
* An array containing the replicated generators.
*/
exportMethod(function tee(iterable, count = 2) {
const iterator = iter(iterable);
const iterables = new Array(count);
const cache = { tail: 0, items: [], returned: MISSING };
while (count--) {
iterables[count] = _tee(iterator, cache);
}
return iterables;
});
/**
* Consumes an iterable and calls a callback for each value.
*
* @param Iterable iterable
* The iterable to test values from.
* @param Function callback
* The callback to call with each value.
* @param Any [thisArg]
* Optional |this| value the callback will be called with.
* @param Any [thisArg]
* Optional |this| value the callback will be called with.
*/
exportMethod(function forEach(iterable, callback, thisArg) {
let index = 0;
for (let item of iterable) {
callback.call(thisArg, item, index++, iterable);
}
});
/**
* Reduce an iterable to a single value by repeatedly calling a callback.
*
* @param Iterable iterable
* The iterable to map values from.
* @param Function callback
* The callback to call with the accumulated value and the current value.
* @param Any [initial]
* Optional initial value. If none is provided, then the first value
* returned from the iterator will be used.
* @return Any
* The final accumulated value.
*/
exportMethod(function reduce(iterable, callback, initial) {
const iterator = iter(iterable);
let current = initial;
if (arguments.length < 3) {
const {value, done} = iterator.next();
if (done) {
return;
}
current = value;
}
for (let item of iterator) {
current = callback(current, item, iterable);
}
return current;
});
/**
* A generator that reduces an iterable to a single value by repeatedly calling
* a callback, yielding the current accumulated value on each iteration.
*
* @param Iterable iterable
* The iterable to map values from.
* @param Function callback
* The callback to call with the accumulated value and the current value.
* @param Any [initial]
* Optional initial value. If none is provided, then the first value
* returned from the iterator will be used.
* @return Chainable
*/
exportMethod(function* ireduce(iterable, callback, initial) {
const iterator = iter(iterable);
let current = initial;
if (arguments.length < 3) {
const {value, done} = iterator.next();
if (done) {
return;
}
current = value;
}
yield current;
for (let item of iterator) {
yield current = callback(current, item, iterable);
}
return current;
});
/**
* Consumes an iterable and returns whether every item passes a callback test
* for truthiness.
*
* @param Iterable iterable
* The iterable to test values from.
* @param Function callback
* The callback to call with each value.
* @param Any [thisArg]
* Optional |this| value the callback will be called with.
* @return Boolean
* Whether every item in the iterable passed the test.
*/
exportMethod(function every(iterable, callback, thisArg) {
let index = 0;
for (let item of iterable) {
if (!callback.call(thisArg, item, index++, iterable)) {
return false;
}
}
return true;
});
/**
* Consumes and iterable and returns whether any item passes a callback test
* for truthiness.
*
* @param Iterable iterable
* The iterable to test values from.
* @param Function callback
* The callback to call with each value.
* @param Any [thisArg]
* Optional |this| value the callback will be called with.
* @return Boolean
* Whether any item in the iterable passed the test.
*/
exportMethod(function some(iterable, callback, thisArg) {
let index = 0;
for (let item of iterable) {
if (callback.call(thisArg, item, index++, iterable)) {
return true;
}
}
return false;
});
/**
* Consumes and iterable and returns the first value to pass the callback test
* for truthiness.
*
* @param Iterable iterable
* The iterable to test values from.
* @param Function callback
* The callback to call with each value.
* @param Any [thisArg]
* Optional |this| value the callback will be called with.
* @return Any
* The first value to pass the test, or undefined.
*/
exportMethod(function find(iterable, callback, thisArg) {
let index = 0;
for (let item of iterable) {
if (callback.call(thisArg, item, index++, iterable)) {
return item;
}
}
});
/**
* Return the first value from a given iterable.
* @param Iterable iterable
* The iterable to get the first value from.
* @param Any [defaultValue]
* The default value to return if the iterable is done.
* @return Any
*/
exportMethod(function first(iterable, defaultValue) {
const iterator = iter(iterable);
const {value, done} = iterator.next();
return done ? defaultValue : value;
});
const iterutils = exports;
const {iter} = iterutils;
const hasOwn = Function.prototype.call.bind(Object.prototype.hasOwnProperty);
function take(iterable, count = Infinity) {
return [...iterutils.slice(iterable, 0, count)];
}
const testStack = [];
let errors = [];
let currentTest;
function test(name, callback) {
testStack.push(name);
try {
callback();
} catch (e) {
errors.push(e);
}
testStack.pop();
}
function ok(value) {
if (!value) {
throw new Error(testStack.join(" ") + " - not ok");
}
}
function strictEqual(a, b) {
return a === b;
}
function similarIterables(a, b, options = {}) {
const comparator = options.comparator || strictEqual;
const count = options.count || Infinity;
const iterA = iter(a);
const iterB = iter(b);
let index = 0;
let isSame = true;
for (;;) {
if (++index >= count) {
break;
}
const {done: doneA, value: valueA} = iterA.next();
const {done: doneB, value: valueB} = iterB.next();
isSame = isSame && comparator(valueA, valueB);
if (doneA || doneB) {
isSame = isSame && doneA === doneB;
break;
}
}
return isSame;
}
function similarProperties(a, b, options = {}) {
if (a === b) {
return true;
}
const comparator = options.comparator || strictEqual;
const keys = Object.keys(a);
const keysCompare = new Set(Object.keys(b));
if (keys.length !== keysCompare.size) {
return false;
}
for (let key of keys) {
if (!keysCompare.has(key) || !comparator(a[key], b[key])) {
return false;
}
}
return true;
}
function similarResult(iterator, {done, value}) {
const result = iterator.next();
return done === result.done && similarProperties(value, result.value);
}
function isDone(iterator, value) {
const result = iterator.next();
return result.done && result.value === value;
}
function isEqual(a, b) {
return a === b;
}
function isInstance(a, b) {
return a instanceof b;
}
function makeAssertable(f) {
return (...args) => {
if (!f(...args)) {
throw new Error(testStack.join(" ") + " - not ok");
}
}
}
const assertSimilarIterables = makeAssertable(similarIterables);
const assertSimilarProperties = makeAssertable(similarProperties);
const assertSimilarResult = makeAssertable(similarResult);
const assertIsDone = makeAssertable(isDone);
const assertIsEqual = makeAssertable(isEqual);
const assertIsInstance = makeAssertable(isInstance);
const assertOk = makeAssertable(Boolean);
function item(value, done) {
return { value: value, done: !!done };
}
function done(value) {
return { value: value, done: true };
}
const testObj = {
a: 5,
b: 10,
__proto__: {
__proto__: null,
c: 20
}
};
test("iter", () => {
// Manually check values here because `similar` uses `iter`.
const iterator = itertools.iter([1, 2, 3]);
assertSimilarResult(iterator, item(1));
assertSimilarResult(iterator, item(2));
assertSimilarResult(iterator, item(3));
assertIsDone(iterator);
});
test("count", () => {
let iterator = itertools.count(5);
assertSimilarIterables(iterator, [5, 6, 7], { count: 3 });
iterator = itertools.count(10, 3);
assertSimilarIterables(iterator, [10, 13, 16], { count: 3 });
});
test("range", () => {
let iterator = itertools.range(3);
assertSimilarIterables(iterator, [0, 1, 2]);
iterator = itertools.range(10, 13);
assertSimilarIterables(iterator, [10, 11, 12]);
iterator = itertools.range(10, 16, 2);
assertSimilarIterables(iterator, [10, 12, 14]);
});
test("repeat", () => {
let iterator = itertools.repeat("f", 3);
assertSimilarIterables(iterator, ["f", "f", "f"]);
});
test("keys", () => {
let iterator = itertools.keys(testObj);
assertSimilarIterables(iterator, ["a", "b"]);
iterator = itertools.keys(testObj, false);
assertSimilarIterables(iterator, ["a", "b", "c"]);
});
test("values", () => {
let iterator = itertools.values(testObj);
assertSimilarIterables(iterator, [5, 10]);
iterator = itertools.values(testObj, false);
assertSimilarIterables(iterator, [5, 10, 20]);
});
test("entries", () => {
let iterator = itertools.entries(testObj);
assertSimilarIterables(iterator, [["a", 5], ["b", 10]]);
iterator = itertools.entries(testObj, false);
assertSimilarIterables(iterator, [["a", 5], ["b", 10], ["c", 20]]);
});
test("exec", () => {
});
test("product", () => {
let iterator = itertools.product("abc", "def");
});
test("dropWhile", () => {
let iterator = itertools.dropWhile([5, 10, 20, 50], v => v < 20);
assertSimilarIterables(iterator, [20, 50]);
});
test("takeWhile", () => {
let iterator = itertools.takeWhile([5, 10, 20, 50], v => v < 20);
assertSimilarIterables(iterator, [5, 10]);
});
test("slice", () => {
let iterator = itertools.slice([5, 10, 20], 1);
assertSimilarIterables(iterator, [10, 20]);
iterator = itertools.slice([5, 10, 20], 1, 2);
assertSimilarIterables(iterator, [10]);
});
test("filter", () => {
let iterator = itertools.filter([5, 10, 20], v => v !== 10);
assertSimilarIterables(iterator, [5, 20]);
});
test("unique", () => {
let iterator = itertools.unique([5, 5, 5, 10, 20, 20]);
assertSimilarIterables(iterator, [5, 10, 20]);
});
test("map", () => {
let iterator = itertools.unique([5, 10, 20], v => v * 10);
assertSimilarIterables(iterator, [50, 100, 200]);
});
test("starMap", () => {
});
test("tap", () => {
});
test("squelch", () => {
});
test("index", () => {
let iterator = itertools.index([5, 10, 20], 1, 2);
assertSimilarIterables(iterator, [[1, 5], [3, 10], [5, 20]]);
});
test("pluck", () => {
const objs = [{ foo: 5 }, { foo: 10 }, { foo: 20 }];
let iterator = itertools.pluck(objs, "foo");
assertSimilarIterables(iterator, [5, 10, 20]);
});
test("invoke", () => {
});
test("cycle", () => {
let iterator = itertools.cycle([5, 10, 20]);
assertSimilarIterables(iterator, [5, 10, 20, 5, 10, 20], { count: 6 });
});
test("chain", () => {
let iterator = itertools.chain([5, 10, 20], "foo");
assertSimilarIterables(iterator, [5, 10, 20, "f", "o", "o"]);
});
test("zip", () => {
let iterator = itertools.zip([5, 10, 20], "foobar");
assertSimilarIterables(iterator, [[5, "f"], [10, "o"], [20, "o"]]);
});
test("zipLongest", () => {
let iterator = itertools.zip([5, 10, 20], "foobar");
assertSimilarIterables(iterator, [[5, "f"], [10, "o"], [20, "o"], [,"b"], [,"a"], [,"r"]]);
});
test("unzip", () => {
let [a, b] = itertools.unzip([[5, "f"], [10, "o"], [20, "o"], [,"b"], [,"a"], [,"r"]]);
assertSimilarIterables(a, [5, 10, 20]);
assertSimilarIterables(b, "foobar");
});
test("compress", () => {
let iterator = itertools.compress([5, 10, 20], [true, false, true]);
assertSimilarIterables(a, [5, 20]);
});
test("groupBy", () => {
});
test("chunk", () => {
});
test("tee", () => {
const iterators = itertools.tee([5, 10, 20], 3);
let count = 0;
for (let iterator of iterators) {
count++;
assertSimilarIterables(iterator, [5, 10, 20]);
}
assertIsEqual(count, 3);
});
test("forEach", () => {
const items = new Set([5, 10, 20]);
itertools.forEach([5, 10, 20], item => {
assertOk(items.has(item));
items.delete(item);
});
assertIsEqual(items.size, 0);
});
test("reduce", () => {
});
test("ireduce", () => {
});
test("every", () => {
});
test("some", () => {
});
test("find", () => {
});
test("first", () => {
let iterator = itertools.iter([5, 10, 20]);
assertIsEqual(iterator.first(), 5);
assertIsEqual(iterator.first(), 10);
assertIsEqual(iterator.first(), 20);
assertIsEqual(iterator.first(), undefined);
assertIsEqual(iterator.first(null), null);
});
test("chaining", () => {
let index = 0;
let iterator = itertools.iter((for (v of [5, 10, 20]) ++index && v));
assertIsEqual(index, 0);
assertIsInstance(iterator, Chainable);
iterator = iterator.cycle(2);
assertIsEqual(index, 0);
assertIsInstance(iterator, Chainable);
iterator = iterator.map(x => x * 10);
assertIsEqual(index, 0);
assertIsInstance(iterator, Chainable);
assertSimilarIterables(iterator, [50, 100, 200, 50, 100, 200]);
assertIsEqual(index, 3);
});
[...exports.count() // count is an infinite generator
.dropWhile(n => n < 64) // limit the start point
.takeWhile(n => n < 91) // limit the end point
.map(String.fromCharCode) // transform
].join("")
// --->
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment