UNPKG

iterablefu

Version:

Small, chainable, set of functions like range, map, reduce, filter, zip, for iterable objects.

288 lines (270 loc) 9.52 kB
import { zipAll } from './generators.js' /** * Converts a sequence of Arrays to a sequence of Objects by assigning the property names * to each array element in turn. The input sequence doesn't have to provide arrays, it can * provide any sequence of iterable objects. * * If the arrays are too long, extra values are ignored. * * If the arrays are too short, the remaining properties are assigned undefined. * * @param {Iterable} propertyNames - a sequence of property names * @param {Iterable} iterable - a sequence of arrays (or any iterable objects) * @return {Generator} for the sequence of Objects * @example * const objects = arrayToObject(['a', 'b'], [[0, 1], [2, 3, 'a'], [4]]) * // objects is [{'a': 0, 'b': 1 }, {'a': 2, 'b': 3 }, {'a': 4, 'b': undefined }] */ export const arrayToObject = function (propertyNames, iterable) { const fn = (iterableElement) => { const outputObject = {} const pairs = zipAll(propertyNames, iterableElement) for (const [propertyName, value] of pairs) { if (propertyName === undefined) continue outputObject[propertyName] = value } return outputObject } // implement arrayToObject in terms of map return map(fn, iterable) } /** * Chunk every n items into an array, and output that array in the output sequence. * * @param {number} n - the number of items to group into each array. * @param {Iterable} iterable - the sequence of items to group * @return {Generator} for the chunked sequence * @example * const a = chunk(2, [0, 1, 2, 3, 4, 5, 6]) * console.log([...a]) // prints [[0, 1], [2, 3], [4, 5], [6]] */ export const chunk = function * (n, iterable) { let accumulator = [] for (const item of iterable) { accumulator.push(item) if (accumulator.length === n) { yield accumulator accumulator = [] } } if (accumulator.length > 0) { yield accumulator } } /** * Execute fn(previous, current) and yields the result for each pair. * Would be useful for calculating time differences between timestamps. * * @param {Function} fn - fn(previous, current), yielding return value * @param {Iterable} iterable - the input iterable * @returns {Generator} - if input has two or more items, output sequence * is one shorter than input sequence. Otherwise, no items are output. * @example * const a = diff((n, m) => m - n, [0, 1, 2, 3, 4]) * console.log([...a]) // prints [1, 1, 1, 1] */ export const diff = function * (fn, iterable) { const iterator = iterable[Symbol.iterator]() let { value, done } = iterator.next() let previousValue = value ;({ value, done } = iterator.next()) while (!done) { yield fn(previousValue, value) previousValue = value ;({ value, done } = iterator.next()) } } /** * Keeps item from input sequence when fn(item) returns truthy. Remove items from input sequence when * fn(item) returns !truthy. * * @param {Function} fn - fn(item) returns truthy when item should be removed * @param {Iterable} iterable - the sequence to filter * @return {Generator} for the filtered sequence * @example * const isEvenNumber = x => x % 2 === 0 * const a = filter(isEvenNumber, [0, 1, 2, 3, 4, 5, 6]) * console.log([...a]) // prints even numbers [0, 2, 4, 6] */ export const filter = function * (fn, iterable) { for (const value of iterable) { if (fn(value) === true) { yield value } } } // These two functions are used by flatten and flattenRecursive. // Generally accepted method of checking if something is a string. const isString = (item) => typeof item === 'string' || item instanceof String // Generally accepted method of checking if something supports the Iterable protocol const isIterable = (item) => item && typeof item[Symbol.iterator] === 'function' export const flattenWhen = function * (fn, iterable) { } /** * Flattens a sequence of items one level deep. It does not flatten strings, even * though they are iterable. * * @param {Iterable} iterable - the iterable sequence to flatten * @returns {Generator} for the flattened sequence * @example * const a = flatten([[0, 1], [2, 3], [4, 5], [6]]) * console.log([...a]) // prints [0, 1, 2, 3, 4, 5, 6] */ export const flatten = function * (iterable) { for (const value of iterable) { if (!isString(value) && isIterable(value)) { yield * value } else { yield value } } } /** * Flattens a sequence by recursively returning items from each iterable in the sequence. * Does not flatten strings even though they are iterable. * * @param {Iterable} iterable - the sequence to flatten * @returns {Generator} for the flattened sequence * @example * const input = [0, [1, 2, 3], [[4, 5], [[[6, 7]], [8, 9], 10]], 11, 12] * const a = flattenRecursive(input) * console.log([...a]) // prints [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] */ export const flattenRecursive = function * (iterable) { for (const value of iterable) { if (!isString(value) && isIterable(value)) { yield * flattenRecursive(value) } else { yield value } } } /** * Generates a sequence of items by calling fn(item) for each item. * * @param {Function} fn - fn(item) returns the output item * @param {Iterable} iterable - the sequence to map * @returns {Generator} for the mapped sequence * @example * const a = map(x => 2 * x, [0, 1, 2, 3]) * console.log([...a]) // prints [0, 2, 4, 6] */ export const map = function * (fn, iterable) { for (const value of iterable) { yield fn(value) } } /** * Map the input sequence to the output sequence with a generator that maps one iterator to another. * * This method exists solely so that ChainableIterable supports chaining for an arbitrary generator function. * * @param {Function} generatorFunction - a function that returns an iterable object, and takes an iterable as a parameter. * Typically, this will be a generator function. * @param {Iterable} iterable - the input sequence * @returns {Generator} for the mapped sequence * @example * const fn = function * (iterable) { * for (let x of iterable) { * yield x * x * } * } * const a = mapWith(fn, [0, 1, 2, 3]) * console.log([...a]) // prints [0, 1, 4, 9] */ export const mapWith = function (generatorFunction, iterable) { return generatorFunction(iterable) } /** * Given a sequence of Arrays, output the nth element of each array as a sequence. * * @param {number} index - the index of the Array to output * @param {Iterable} iterable - the iterable to process * @returns {Generator} for the nth elements * @example * const input = [[0, 1], [2, 3], [4, 5]] * const a = nth(1, input) * console.log([...a]) // prints [1, 3, 5] */ export const nth = (index, iterable) => { return map(x => (index < 0) ? x[x.length + index] : x[index], iterable) } /** * Given a sequence of Objects, output the specified property of each Object as a sequence. * * @param {string} propertyname - the property to extract from each Object * @param {Iterable} iterable - the input sequence of Objects * @returns {Generator} for the plucked items * @example * const input = [{'a': 1, 'b': 2}, {'a': 3, 'b': 4}, {'a': 5, 'b': 6}] * const a = pluck('a', input) * console.log([...a]) // prints [1, 3, 5] */ export const pluck = (propertyname, iterable) => { return map(x => x[propertyname], iterable) } /** * Reject items when fn(item) returns truthy. * * @param {Function} fn - fn(item) returns truthy when item should be removed from output sequence * @param {Iterable} iterable - input sequence * @returns {Generator} for the non-rejected items * @example * const isEvenNumber = x => x % 2 === 0 * const a = reject(isEvenNumber, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) * console.log([...a]) // prints [1, 3, 5, 7, 9] */ export const reject = (fn, iterable) => { return filter(x => fn(x) !== true, iterable) } /** * Create an output sequence that is the first n items of the input sequence. * * @param {number} n - the number of items to take * @param {Iterable} iterable - the input sequence to take items from * @returns {Generator} for the first n items * @example * const a = take(5, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) * console.log([...a]) // prints [0, 1, 2, 3, 4] */ export const take = function * (n, iterable) { const iterator = iterable[Symbol.iterator]() let taken = 0 let next while (taken < n) { next = iterator.next() taken += 1 if (next.done === true) break yield next.value } } /** * Output items from the input iterable until fn(item) returns !truthy. * * @param {Function} fn - fn(item) returns truthy to put item in the output sequence * @param {Iterable} iterable - input sequence * @returns {Generator} for the selected items * @example * const a = takeWhile(x => x != 4, [0, 1, 2, 3, 4, 5, 6]) * console.log([...a]) // prints [0, 1, 2, 3] */ export const takeWhile = function * (fn, iterable) { for (const item of iterable) { if (fn(item) !== true) break yield item } } /** * Pass the input sequence to the output sequence without change, but execute `fn(item)` for each * item in the sequence. * * @param {Function} fn - `fn(item)` is called for each item in the sequence * @param {Iterable} iterable - the input sequence * @returns {Generator} that is equivalent to the input iterable * @example * const a = tap(console.log, [1, 2, 3, 4, 5]) * [...a] // prints [1, 2, 3, 4, 5] */ export const tap = (fn, iterable) => { return map(x => { fn(x); return x }, iterable) }