UNPKG

deleight

Version:

A library with 9 modules for writing more expressive web applications with traditional HTML, CSS and JavaScript.

408 lines (407 loc) 10.8 kB
"use strict"; /** * This module exports many useful generators for JS applications. * There are generators for most array methods like `map` and `filter`, * along many those drawn from other languages that make heavy use * of generators, like Python. * * Generators are very useful for improving performance as they help to * avoid wasteful computations and storage. They were important factors * in the speed and memory efficiency of some of the best performing * libraries in the * [JS framework benchmarks](https://github.com/krausest/js-frameworks-benchmark) * * * @module */ Object.defineProperty(exports, "__esModule", { value: true }); exports.random = exports.zip = exports.zipFlat = exports.forNext = exports.next = exports.repeat = exports.items = exports.range = exports.forceIterator = exports.chain = exports.reduce = exports.filter = exports.map = exports.forAwait = exports.forEachAsync = exports.forEach = exports.flat = void 0; /** * Similar to `Array.flat`. Pending tests. Please report bugs. * * @example * * * @param it */ function* flat(it) { for (let item of it) { if ((typeof item === 'object' || item instanceof Function) && Reflect.has(item, Symbol.iterator)) { yield* flat(item); } else yield item; } } exports.flat = flat; /** * Performs the given action for all items of the iterable. * * @example * import { forEach } from 'deleight/generators'; * forEach(range(10), ()=> console.log(i)); * * @param it * @param action */ function forEach(it, action) { let i = 0; for (let item of it) action(item, i++); } exports.forEach = forEach; /** * Performs the given action for all items of the async iterable. * * @example * import { forEachAsync } from 'deleight/generators'; * async function* asyncGen() { * yield *range(10); * } * forEachAsync(asyncGen(), ()=> console.log(i)); * * @param it * @param action */ async function forEachAsync(it, action) { let i = 0; for await (let item of it) action(item, i++); } exports.forEachAsync = forEachAsync; /** * Converts the async iterable to a promise that resolves to an * array. * * @example * import { forAwait } from 'deleight/generators'; * async function* asyncGen() { * yield *range(10); * } * await forAwait(asyncGen()); // [0, 1, ..., 9] * * @param it */ async function forAwait(it) { const result = []; for await (let item of it) result.push(item); return result; } exports.forAwait = forAwait; /** * Maps the values of the iterable to other values using the given mapper. * * @example * import { map } from 'deleight/generators'; * map(range(10), i=> 2 * i); * * @param it * @param mapper */ function* map(it, mapper) { let i = 0; for (let item of it) yield mapper(item, i++); } exports.map = map; /** * Filters the values of the iterable using the given test function. * * @example * import { filter } from 'deleight/generators'; * filter(range(10), i=> 2 < i); * * @param it * @param test */ function* filter(it, test) { let i = 0; for (let item of it) if (test(item, i++)) yield item; } exports.filter = filter; /** * Reduces the iterable using the given reducer function. * * @example * import { reduce } from 'deleight/generators'; * reduce(range(10), ((r, i) => r * i), 3); * * @param it * @param reducer */ function reduce(it, reducer, value) { let i = 0; for (let item of it) value = reducer(value, item, i++); return value; } exports.reduce = reduce; /** * Chains multiple iterables... * * @example * import { chain } from 'deleight/generators'; * for (let i of chain(range(10), range(15))) { * console.log(i); // 0, 1, 2, 3,...9, 0, 1, 2, ..., 14 * } * * @param its */ function* chain(...its) { let item; for (let arg of its) { for (item of arg) yield item; } } exports.chain = chain; /** * All combinations of items from each input iterable. * * @example * * * @param its */ function* product(...its) { const breaker = Symbol(); const its2 = its.map(arg => repeat(arg, -1, breaker)); let c2 = its.length - 1, c1; let it, value; let item = its2.map(it => it.next().value); for (let value of item) if (value === breaker) return; // empty. while (true) { yield item; it = its2[c2]; while ((value = it.next().value) !== breaker) { item[c2] = value; yield item; } c1 = c2; do { it = its2[--c1]; value = it.next().value; } while (value === breaker && c1 > 0); if (c1 >= 0 && value !== breaker) item[c1] = value; else break; } } /** * Forces any iterable or iterator to become an iterator. Will throw * if this is not possible. * * @example * import { forceIterator } from 'deleight/generators'; * const it = forceIterator([1, 2, 3, 4, 5]); * * @param it */ function forceIterator(it) { return it[Symbol.iterator]?.() || it; } exports.forceIterator = forceIterator; /** * Fast and 'costless' range function for javascript based on generators. * * @example * import { range } from 'deleight/generators'; * const arr1000 = [...range(0, 1000)]; * // creates an array with 1000 items counting from 0 to 999. * * @param {number} start * @param {number} [end] * @param {number} [step] */ function* range(start, end, step) { if (!step) step = 1; if ((end === undefined || end === null) && start) { end = start; start = 0; } for (let i = start; i < end; i += step) yield i; } exports.range = range; /** * Returns a generator which iterates over the subset of the * 'arrayLike' object that matches the provided index. * * @example * import { items, range } from 'deleight/generators'; * const tenth = []...items(range(1000), range(0, 1000, 10))]; * // selects every 10th item in the array. * * @param {any} it * @param {Iterable<any>} index */ function* items(it, index) { for (let i of index) yield it[i]; } exports.items = items; /** * Returns a generator that yields first argument (`what`) the number of * times specified by the second argument (`times`). If `times` is not * given, `what` is repeated indefinitely. * * @example * import { repeat } from 'deleight/generators'; * const repeated = [...repeat([1, 2, 3], 4)]; * // [1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3] * * @param {Iterable<any>} it * @param {number} [times] */ function* repeat(it, times = -1, ...extras) { let item; if (times !== 1 && !(it instanceof Array) && !(it instanceof Function)) { it = [...it]; } let what2; if (times < 0) { while (true) { what2 = (it instanceof Function) ? it() : it; for (item of what2) yield item; } } else for (let i = 0; i < times; i++) { what2 = (it instanceof Function) ? it() : it; for (item of what2) yield item; for (item of extras) yield item; } } exports.repeat = repeat; /** * Returns a Generator over the next 'count' items of the iterable or iterator. * In most cases you will call this function with an iterator. * * If a firstValue is specified, it will be yielded first. * * @example * import { next, forceIterator } from 'deleight/generators'; * const it = forceIterator([1, 'a', 2, 'b', 3, 'c', 4, 'd']); * const [num1, let1] = next(it, 2); // (1, 'a') * const [num2, let2] = next(it, 2); // (2, 'b') * * @param it * @param count * @param firstValue */ function* next(it, count, firstValue) { it = forceIterator(it); let count2 = count; if (firstValue) { yield firstValue; count2--; } while (count2-- > 0) yield it.next().value; } exports.next = next; /** * Returns a generator of arrays with the next 'count' items of * the given iterable. In other words, this function will partition the * input iterable with each partition containing `count` items. * * @example * import { forNext } from 'deleight/generators'; * const it = [1, 'a', 2, 'b', 3, 'c', 4, 'd']; * const it2 = forNext(it, 2); // (1, 'a'), (2, 'b'), ... * * @param { Iterable<any> } it * @param { number } count */ function* forNext(it, count) { const it2 = forceIterator(it); let nextItem = it2.next(); while (!nextItem.done) { yield [...next(it2, count, nextItem.value)]; nextItem = it2.next(); } } exports.forNext = forNext; /** * Returns a generator over the items of all the input args (iterables), starting from * the zero index to the maximum index of the smallest arg. * * The effective length of the generator is the multiple of the length of the smallest * arg and the number of args. * * @example * import { zipFlat } from 'deleight/generators'; * for (let i of zipFlat(range(10), range(15))) { * console.log(i); // (0, 0, 1, 1, 2, 2, .... till range(10) is exhausted. * } * * @param its */ function* zipFlat(...its) { const count = its.length; const itArgs = its.map(arg => forceIterator(arg)); let i, nextItem; while (true) { for (i = 0; i < count; i++) { nextItem = itArgs[i].next(); if (nextItem.done) return; else yield nextItem.value; } } } exports.zipFlat = zipFlat; /** * Returns a generator over the combined items of all the input args (iterables), * starting from the zero index to the maximum index of the smallest arg. * * The effective length of the generator is the length of the smallest input * iterable. * * @example * import { zip } from 'deleight/generators'; * for (let i of zip(range(10), range(15))) { * console.log(i); // (0, 0), (1, 1), (2, 2), .... till range(10) is exhausted. * } * * @param its */ function* zip(...its) { yield* forNext(zipFlat(...its), its.length); } exports.zip = zip; /** * Returns an unordered/random generator over the input itrable. * Note that this is forced to materialize the input before running. * * * @example * import { random } from 'deleight/generators'; * const unOrdered = random([1, 2, 3, 4]); // probably [4, 1, 3, 2] * * @param it The iterable to get items from * @param count The number of items to return. All items are returned if * count < 0. Default is -1 */ function* random(it, count = -1) { if (count === 0) return; const arr = [...it]; for (let i = arr.length - 1; i >= 0; i--) { yield arr.splice(Math.round(Math.random() * i), 1)[0]; if (--count === 0) return; } } exports.random = random;