/** * Empty iterator */ export const IATEND: Iterator = { next: function () { return { done: true, value: undefined }; }, }; /** * Empty iterable */ export const IEMPTY: Iterable = { [Symbol.iterator]: () => IATEND, }; /** * Iterable constructor, from an initial value, and a step value */ export function irecur(start: T, step: (a: T) => T | null): Iterable { return { [Symbol.iterator]: function* () { let val: T | null = start; do { yield val; val = step(val); } while (val !== null); }, }; } /** * Iterable constructor, from an array * * The iterator will yield the next value each time it is called, then undefined when the array's end is reached. */ export function iarray(array: T[], offset = 0): Iterable { return { [Symbol.iterator]: function () { return array.slice(offset)[Symbol.iterator](); }, }; } /** * Iterable constructor, from a single value * * The value will be yielded only once, not repeated over. */ export function isingle(value: T): Iterable { return iarray([value]); } /** * Iterable that repeats the same value. */ export function irepeat(value: T, count = -1): Iterable { return { [Symbol.iterator]: function* () { let n = count; while (n != 0) { yield value; n--; } }, }; } /** * Equivalent of Array.forEach for all iterables. * * If the callback returns *stopper*, the iteration is stopped. */ export function iforeach( iterable: Iterable, callback: (_: T) => any, stopper: any = null, ): void { for (let value of iterable) { if (callback(value) === stopper) { break; } } } /** * Returns the first item passing a predicate */ export function ifirst( iterable: Iterable, predicate: (item: T) => boolean, ): T | null { for (let value of iterable) { if (predicate(value)) { return value; } } return null; } /** * Returns the first non-null result of a value-yielding predicate, applied to each iterator element */ export function ifirstmap( iterable: Iterable, predicate: (item: T1) => T2 | null, ): T2 | null { for (let value of iterable) { let res = predicate(value); if (res !== null) { return res; } } return null; } /** * Materialize an array from consuming an iterable * * To avoid materializing infinite iterators (and bursting memory), the item count is limited to 1 million, and an * exception is thrown when this limit is reached. */ export function imaterialize(iterable: Iterable, limit = 1000000): T[] { let result: T[] = []; for (let value of iterable) { result.push(value); if (result.length >= limit) { throw new Error("Length limit on iterator materialize"); } } return result; } /** * Count items in an iterator * * To avoid counting infinite iterators (and bursting memory), the item count is limited by * the *limit* parameter, and Infinity will be returned after that many. */ export function ilength(iterable: Iterable, limit = 1000000): number { let result = 0; for (let _ of iterable) { if (result >= limit) { return Infinity; } result += 1; } return result; } /** * Iterate over natural integers * * If *count* is not specified, the iterator is infinite */ export function irange( count: number = -1, start = 0, step = 1, ): Iterable { return { [Symbol.iterator]: function* () { let i = start; let n = count; while (n != 0) { yield i; i += step; n--; } }, }; } /** * Iterate over numbers, by applying a step taken from an other iterator * * This iterator stops when the "step iterator" stops * * With no argument, istep() == irange() */ export function istep(start = 0, step_iterable = irepeat(1)): Iterable { return { [Symbol.iterator]: function* () { let i = start; yield i; for (let step of step_iterable) { i += step; yield i; } }, }; } /** * Skip a given number of values from an iterator, discarding them. */ export function iskip(iterable: Iterable, count = 1): Iterable { return { [Symbol.iterator]: function () { let iterator = iterable[Symbol.iterator](); let n = count; while (n-- > 0) { iterator.next(); } return iterator; }, }; } /** * Return the value at a given position in the iterator */ export function iat(iterable: Iterable, position: number): T | null { if (position < 0) { return null; } else { if (position > 0) { iterable = iskip(iterable, position); } let iterator = iterable[Symbol.iterator](); let state = iterator.next(); return state.done ? null : state.value; } } /** * Chain an iterable of iterables. * * This will yield values from the first yielded iterator, then the second one, and so on... */ export function ichainit(iterables: Iterable>): Iterable { return { [Symbol.iterator]: function* () { for (let iterable of iterables) { for (let value of iterable) { yield value; } } }, }; } /** * Chain iterables. * * This will yield values from the first iterator, then the second one, and so on... */ export function ichain(...iterables: Iterable[]): Iterable { if (iterables.length == 0) { return IEMPTY; } else { return ichainit(iterables); } } /** * Loop an iterator for a number of times. * * If count is negative, if will loop forever (infinite iterator). * * onloop may be used to know when the iterator resets. */ export function iloop( base: Iterable, count = -1, onloop?: Function, ): Iterable { return { [Symbol.iterator]: function* () { let n = count; let start = false; while (n-- != 0) { for (let value of base) { if (start) { if (onloop) { onloop(); } start = false; } yield value; } start = true; } }, }; } /** * Iterator version of "map". */ export function imap( iterable: Iterable, mapfunc: (_: T1) => T2, ): Iterable { return { [Symbol.iterator]: function* () { for (let value of iterable) { yield mapfunc(value); } }, }; } /** * Iterator version of "reduce". */ export function ireduce( iterable: Iterable, reduce: (item1: T, item2: T) => T, init: T, ): T { let result = init; for (let value of iterable) { result = reduce(result, value); } return result; } /** * Iterator version of "filter". */ export function ifilter( iterable: Iterable, filterfunc: (_: T) => boolean, ): Iterable { return { [Symbol.iterator]: function* () { for (let value of iterable) { if (filterfunc(value)) { yield value; } } }, }; } /** * Apply map, and filter invalid results in the same pass. */ export function imapfilter( iterable: Iterable, mapfunc: (_: T1) => T2 | null, ): Iterable> { return { [Symbol.iterator]: function* () { for (let value of iterable) { const mapped = mapfunc(value); if (mapped !== null) { yield mapped as Exclude; } } }, }; } /** * Type filter, to return a list of instances of a given type */ export function ifiltertype( iterable: Iterable, filter: (item: any) => item is T, ): Iterable { return ifilter(iterable, filter); } /** * Class filter, to return a list of instances of a given type */ export function ifilterclass( iterable: Iterable, classref: { new (...args: any[]): T }, ): Iterable { return ifilter(iterable, (item): item is T => item instanceof classref); } /** * Combine two iterables. * * This iterates through the second one several times, so if one iterator may be infinite, * it should be the first one. */ export function icombine( it1: Iterable, it2: Iterable, ): Iterable<[T1, T2]> { return ichainit(imap(it1, (v1) => imap(it2, (v2): [T1, T2] => [v1, v2]))); } /** * Enumerate another iterable, adding the index before the value. */ export function ienumerate(it: Iterable): Iterable<[number, T]> { return { [Symbol.iterator]: function* () { const iterator = it[Symbol.iterator](); let state = iterator.next(); let idx = 0; while (!state.done) { yield [idx, state.value]; state = iterator.next(); idx += 1; } }, }; } /** * Advance through two iterables at the same time, yielding item pairs * * Iteration will stop at the first of the two iterators that stops. */ export function izip( it1: Iterable, it2: Iterable, ): Iterable<[T1, T2]> { return { [Symbol.iterator]: function* () { let iterator1 = it1[Symbol.iterator](); let iterator2 = it2[Symbol.iterator](); let state1 = iterator1.next(); let state2 = iterator2.next(); while (!state1.done && !state2.done) { yield [state1.value, state2.value]; state1 = iterator1.next(); state2 = iterator2.next(); } }, }; } /** * Advance two iterables at the same time, yielding item pairs (greedy version) * * Iteration will stop when both iterators are consumed, returning partial couples (undefined in the peer) if needed. */ export function izipg( it1: Iterable, it2: Iterable, ): Iterable<[T1 | undefined, T2 | undefined]> { return { [Symbol.iterator]: function* () { let iterator1 = it1[Symbol.iterator](); let iterator2 = it2[Symbol.iterator](); let state1 = iterator1.next(); let state2 = iterator2.next(); while (!state1.done || !state2.done) { yield [state1.value, state2.value]; state1 = iterator1.next(); state2 = iterator2.next(); } }, }; } /** * Partition in two iterables, one with values that pass the predicate, the other with values that don't */ export function ipartition( iterable: Iterable, predicate: (item: T) => boolean, ): [Iterable, Iterable] { return [ ifilter(iterable, predicate), ifilter(iterable, (x) => !predicate(x)), ]; } /** * Alternate between several iterables (pick one from the first one, then one from the second...) */ export function ialternate(iterables: Iterable[]): Iterable { return { [Symbol.iterator]: function* () { let iterators = iterables.map((iterable) => iterable[Symbol.iterator]()); let done: boolean; do { done = false; // TODO Remove "dried-out" iterators for (let iterator of iterators) { let state = iterator.next(); if (!state.done) { done = true; yield state.value; } } } while (done); }, }; } /** * Yield items from an iterator only once. * * Beware that even if this function is not materializing, it keeps track of yielded item, and may choke on * infinite or very long streams. Thus, no more than *limit* items will be yielded (an error is thrown * when this limit is reached). * * This function is O(n²) */ export function iunique( iterable: Iterable, limit = 1000000, ): Iterable { return { [Symbol.iterator]: function* () { let done: Set = new Set(); let n = limit; for (let value of iterable) { if (!done.has(value)) { if (n-- > 0) { done.add(value); yield value; } else { throw new Error("Unique count limit on iterator"); } } } }, }; } /** * Common reduce shortcuts */ export const isum = (iterable: Iterable) => ireduce(iterable, (a, b) => a + b, 0); export const icat = (iterable: Iterable) => ireduce(iterable, (a, b) => a + b, ""); export const imin = (iterable: Iterable) => ireduce(iterable, Math.min, Infinity); export const imax = (iterable: Iterable) => ireduce(iterable, Math.max, -Infinity); /** * Min/max by key function */ export function iminBy( iterable: Iterable, key: (value: T) => number, ): T | undefined { return ireduce( iterable, (item1, item2) => (typeof item1 == "undefined" || typeof item2 == "undefined") || (key(item1) > key(item2)) ? item2 : item1, undefined, ); } export function imaxBy( iterable: Iterable, key: (value: T) => number, ): T | undefined { return iminBy(iterable, (value) => -key(value)); }