// Type definitions interface Mapper { (x: T): U; } interface FlatMapper { (x: T): Iterable; } interface Predicate { (x: T): boolean; } interface Guard { (a: T): a is U; } interface Reducer { (acc: U, x: T): U; } interface Collector { from: (i: Iterable) => U; }; /** * Useful operators for Iterables. * * The module exports the class `Seq` which creates a wrapper that exposes * a fluent interface for traversing and manipulating the underlying iterable * object using operators. * * The module also exports each operator as a function for use in isolation. * * All methods and functions return a new Iterable object. Iteration is lazy * and will only execute when a value is requested (toArray, reduce, etc). * The iteration will terminate as early as possible. * * For example, the following `Seq` code will only iterate 3 times: * * let array = []; * for (let i = 1; i <= 1000; i++) { * array.push(i); * } * * // 3 iterations * Seq.from(array) * .map(n => n * n) * .filter(n => n % 2 === 1) * .takeUntil(n => n > 30) * .toArray() // [ 1, 9, 25 ] * */ /** * Wraps `iterable` in an object with collection operations. */ export class Seq { private static readonly EMPTY = new Seq([]); // constructors static empty() { return Seq.EMPTY as Seq; } static of(...values: T[]) { return (values.length === 0 ? Seq.empty() : new Seq(values)) as Seq; } static from(iterable: Iterable) { return new Seq(iterable); } static concat(...iterables: Iterable[]) { return Seq.from(concat(...iterables)); } private _iterator?: Iterator; private readonly _cache: T[] = []; private constructor(private _iterable: Iterable) { } *[Symbol.iterator]() { // Since this._iterator can be a generator object, we cache the iteration // so it can be replayed. Generator objects are stateful, so this is // necessary. yield* this._cache; if (this._iterator == null) { this._iterator = this._iterable[Symbol.iterator](); } while (true) { const { done, value } = this._iterator.next(); if (done) { break; } else { this._cache.push(value); yield value; } } } concat(...iterables: Iterable[]) { return new Seq(concat(this, ...iterables)); } map(fn: Mapper) { return new Seq(map(fn, this)); } flatMap(fn: FlatMapper) { return new Seq(flatMap(fn, this)); } uniq() { return new Seq(uniq(this)); } uniqBy(fn: Mapper) { return new Seq(uniqBy(fn, this)); } orderBy(fn: Mapper, reverse = false) { const reverseBit = reverse ? -1 : 1; return new Seq(this.toArray().sort((a: T, b: T) => { const mappedA = fn(a); const mappedB = fn(b); return mappedA < mappedB ? -1 * reverseBit : mappedA > mappedB ? 1 * reverseBit : 0; })) } filter(fn: Guard): Seq; filter(fn: Predicate): Seq; filter(fn: Predicate) { return new Seq(filter(fn, this)); } take(n: number) { return new Seq(take(n, this)); } takeLast(n: number) { return new Seq(takeLast(n, this)); } takeWhile(fn: Guard): Seq; takeWhile(fn: Predicate): Seq takeWhile(fn: Predicate) { return new Seq(takeWhile(fn, this)); } drop(n: number) { return new Seq(drop(n, this)); } dropLast(n: number) { return new Seq(dropLast(n, this)); } dropWhile(fn: Guard): Seq; dropWhile(fn: Predicate): Seq; dropWhile(fn: Predicate) { return new Seq(dropWhile(fn, this)); } find(fn: Guard): U; find(fn: Predicate): T; find(fn: Predicate) { return find(fn, this); } findLast(fn: Guard): U; findLast(fn: Predicate): T; findLast(fn: Predicate) { return findLast(fn, this); } every(fn: Predicate) { return every(fn, this); } some(fn: Predicate) { return some(fn, this); } includes(item: T) { return includes(item, this); } reduce(fn: Reducer, value: U): U { return reduce(fn, value, this); } join(separator: string) { return join(separator, this); } toArray() { return this.reduce((arr: T[], item: T) => (arr.push(item), arr), []); } into(Collector: Collector) { return Collector.from(this); } first() { return first(this); } last() { return last(this); } rest() { return rest(this); } isEmpty() { return !this.some(() => true); } forEach(fn: (t:T) => void) { for (let iter = this[Symbol.iterator]();;) { let { done, value } = iter.next(); if (done) break; fn(value); } return this; } groupBy(fn: (t: T) => S) { const groupMap = this.reduce((map: Map, entry: T) => { const key = fn(entry); if (!map.has(key)) map.set(key, []); const group: T[] = map.get(key) as T[]; group.push(entry); return map; }, new Map()); return Seq.from(groupMap); } } // XXX The for..of loop construct is not being used because babel adds a // try-catch to the loop body, which deoptimizes the code path. See // https://github.com/google/traceur-compiler/issues/1773. export function* concat(...iterables: Iterable[]) { for (let i = 0; i < iterables.length; i++) { yield* iterables[i]; } } export function* map(fn: Mapper, iterable: Iterable) { for (let iter = iterable[Symbol.iterator]();;) { let { done, value } = iter.next(); if (done) break; yield fn(value); } } export function* flatMap(fn: FlatMapper, iterable: Iterable) { for (let iter = iterable[Symbol.iterator]();;) { let { done, value } = iter.next(); if (done) break; yield* fn(value); } } export function* uniq(iterable: Iterable) { let values = new Set(); for (let iter = iterable[Symbol.iterator]();;) { let { done, value } = iter.next(); if (done) break; if (values.has(value) === false) { values.add(value); yield value; } } } export function* uniqBy(fn: Mapper, iterable: Iterable) { let keys = new Set(); for (let iter = iterable[Symbol.iterator]();;) { let { done, value } = iter.next(); if (done) break; let key = fn(value); if (keys.has(key) === false) { keys.add(key); yield value; } } } export function filter(fn: Guard, iterable: Iterable): Iterable; export function filter(fn: Predicate, iterable: Iterable): Iterable; export function* filter(fn: Predicate, iterable: Iterable) { for (let iter = iterable[Symbol.iterator]();;) { let { done, value } = iter.next(); if (done) break; if (fn(value)) yield value; } } export function* take(n: number, iterable: Iterable) { let count = 0; for (let iter = iterable[Symbol.iterator]();;) { let { done, value } = iter.next(); if (!done && count++ < n) yield value; else break; } } /** * Take the last `n` elements of `iterable` */ export function* takeLast(n: number, iterable: Iterable) { yield* Array.from(iterable).slice(-Math.max(0, n)); } /** * Keep items until test returns false. */ export function takeWhile(fn: Guard, iterable: Iterable): Iterable; export function takeWhile(fn: Predicate, iterable: Iterable): Iterable; export function* takeWhile(fn: Predicate, iterable: Iterable) { for (let iter = iterable[Symbol.iterator]();;) { let { done, value } = iter.next(); if (done || fn(value) === false) break; yield value; } } export function* drop(n: number, iterable: Iterable) { for (let iter = iterable[Symbol.iterator]();;) { let { done, value } = iter.next(); if (done) break; if (n-- > 0) continue; yield value; } } export function* dropLast(n: number, iterable: Iterable) { yield* Array.from(iterable).slice(0, -Math.max(0, n)); } /** * Ignore items until test returns false. */ export function dropWhile(fn: Guard, iterable: Iterable): Iterable; export function dropWhile(fn: Predicate, iterable: Iterable): Iterable; export function* dropWhile(fn: Predicate, iterable: Iterable) { let take = false; for (let iter = iterable[Symbol.iterator]();;) { let { done, value } = iter.next(); if (done) break; if (take === false) take = !fn(value); if (take === true) yield value; } } // Return values -- an item from iterable collection, or a reduction /** * Find the first item that test returns true for. */ export function find(test: Guard, iterable: Iterable): U; export function find(test: Predicate, iterable: Iterable): T; export function find(test: Predicate, iterable: Iterable) { for (let iter = iterable[Symbol.iterator]();;) { let { done, value } = iter.next(); if (done) break; if (test(value) === true) return value; } return undefined; } /** * Find the last item that the test returns true for. */ export function findLast(test: Guard, iterable: Iterable): U; export function findLast(test: Predicate, iterable: Iterable): T; export function findLast(test: Predicate, iterable: Iterable) { let last: T|void; for (let iter = iterable[Symbol.iterator]();;) { let { done, value } = iter.next(); if (done) break; if (test(value)) last = value; } return last; } export function first(iterable: Iterable) { return iterable[Symbol.iterator]().next().value; } export function last(iterable: Iterable) { let last: T|void; for (let iter = iterable[Symbol.iterator]();;) { let { done, value } = iter.next(); if (done) break; last = value; } return last; } export function rest(iterable: Iterable) { return drop(1, iterable); } export function includes(item: T, iterable: Iterable): boolean { return some(t => t === item, iterable); } export function every(test: Predicate, iterable: Iterable): boolean { for (let iter = iterable[Symbol.iterator]();;) { let { done, value } = iter.next(); if (done) break; if (test(value) === false) return false; } return true; } export function some(test: Predicate, iterable: Iterable): boolean { for (let iter = iterable[Symbol.iterator]();;) { let { done, value } = iter.next(); if (done) break; if (test(value) === true) return true; } return false; } /** * Reduce collection to a single value. */ export function reduce(fn: Reducer, seedValue: U, iterable: Iterable): U { let result = seedValue for (let iter = iterable[Symbol.iterator]();;) { let { done, value } = iter.next(); if (done) break; result = fn(result, value); } return result; } export function join(separator: string, iterable: Iterable): string { return Array.from(iterable).join(separator); }