Skip to main content
Module

x/lazy/lib/iterators.ts

A linq-like lazy-evaluation enumerable/iteration library that aims to support deno, node & browser
Latest
File
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037
import type { MapFn } from './aggregates.ts';import { toArray, toMap } from './aggregates.ts';
// Helpers types.
/** * A function that maps one type to another, and is given the index that * the iterator is currently on. */export type IndexMapFn<TSource, TResult> = ( source: TSource, index: number,) => TResult;/** * A function that combines 2 types into another. */export type CombineFn<TFirst, TSecond, TResult> = ( first: TFirst, second: TSecond,) => TResult;/** * A function that takes in 2 values and returns the sorting number. */export type SortFn<TSource> = (a: TSource, b: TSource) => number;/** * A function that takes in a value and an index and returns a boolean. */export type IndexPredicate<TSource> = ( element: TSource, index: number,) => boolean;/** * A function that takes in a value and an index and returns whether the * value is of a given type. */export type IndexIsPredicate<TSource, TResult extends TSource> = ( element: TSource, index: number,) => element is TResult;
/** * A grouping of elements based on the key. */export interface IGrouping<TKey, TElement> { key: TKey; elements: Iterable<TElement>;}
// Done helpers.
/** * Always returns done: true. * @hidden */function doneNext(): IteratorResult<any> { return { done: true, value: undefined };}
/** * Returns the standard done result, and sets the next function * on the current iterator to done that always returns done: true. * @remarks This will not modify the classes, since the next functions * on them are on the prototype, and this only modifies the current object. * @hidden */function done(iterator: Iterator<any>): IteratorResult<any> { iterator.next = doneNext; return doneNext();}
// Helper classes.
/** * @hidden */class Queue<T> { private static readonly _maxLeftoverCount = 10000;
private _buffer: T[] = []; private _front = 0;
public get length(): number { return this._buffer.length - this._front; }
public enqueue(element: T): void { this._buffer.push(element); }
public dequeue(): T { if (this.length === 0) { throw new Error('Cannot dequeue an empty queue'); }
const element = this._buffer[this._front];
// Memory & peformance enhancement thanks to // http://code.iamkate.com/javascript/queues/#implementation if (++this._front * 2 >= this._buffer.length) { this._buffer = this._buffer.slice(this._front); this._front = 0; } else if (this._front >= Queue._maxLeftoverCount) { // If the front is getting too large, start de-referencing items // so that we can start GC on them and don't build up too much // unused memory. this._buffer[this._front] = undefined as any; }
return element; }}
// Base Iterators
/** * @hidden */export class LazyRangeIterator implements Iterator<number> { private _index: number; private readonly _direction: number;
public constructor(_start: number, private readonly _end: number) { this._index = _start; this._direction = _end < _start ? -1 : 1; }
public next(): IteratorResult<number> { if ( this._direction > 0 ? this._index >= this._end : this._index <= this._end ) { return done(this); } else { const nextResult = { done: false, value: this._index } as const; this._index += this._direction; return nextResult; } }}
/** * @hidden */export class LazyRepeatIterator<TElement> implements Iterator<TElement> { private _index = 0;
public constructor( private readonly _element: TElement, private readonly _count: number, ) {}
public next(): IteratorResult<TElement> { if (this._index >= this._count) { return done(this); } else { const nextResult = { done: false, value: this._element } as const; this._index++; return nextResult; } }}
// Iterators
/** * @hidden */export class LazyAppendPrependIterator<TElement> implements Iterator<TElement> { private readonly _iterator: Iterator<TElement>; private _started = false; private _finished = false;
public constructor( iterable: Iterable<TElement>, private readonly _element: TElement, private readonly _atStart: boolean, ) { this._iterator = iterable[Symbol.iterator](); }
public next(): IteratorResult<TElement> { if (this._finished) { return done(this); }
if (!this._started) { this._started = true;
if (this._atStart) { return { done: false, value: this._element }; } }
const result = this._iterator.next();
if (result.done) { this._finished = true;
if (!this._atStart) { return { done: false, value: this._element }; } else { return done(this); } } else { return result; } }}
/** * @hidden */export class LazyBatchInIterator<TElement> implements Iterator<Iterable<TElement>> { private readonly _iterator: Iterator<TElement>; private _done = false;
public constructor( iterable: Iterable<TElement>, private readonly _batchSize: number, private readonly _includeIncomplete: boolean, ) { this._iterator = iterable[Symbol.iterator](); }
public next(): IteratorResult<Iterable<TElement>> { if (this._done) { return done(this); }
const batch: TElement[] = [];
while (true) { const result = this._iterator.next();
if (result.done) { this._done = true;
if (batch.length > 0 && this._includeIncomplete) { return { done: false, value: batch }; } else { return done(this); } } else { batch.push(result.value);
if (batch.length === this._batchSize) { return { done: false, value: batch }; } } } }}
/** * @hidden */export class LazyConcatIterator<TElement> implements Iterator<TElement> { private readonly _iterators: Array<Iterator<TElement>> = []; private _current = 0;
public constructor(iterables: Array<Iterable<TElement>>) { for (const iterable of iterables) { this._iterators.push(iterable[Symbol.iterator]()); } }
public next(): IteratorResult<TElement> { if (this._current >= this._iterators.length) { return done(this); }
while (this._current < this._iterators.length) { const result = this._iterators[this._current].next();
if (!result.done) { return result; } else { this._current++; } }
return done(this); }}
/** * @hidden */export class LazyDefaultIfEmptyIterator<TElement> implements Iterator<TElement> { private readonly _iterator: Iterator<TElement>; private _started = false; private _done = false;
public constructor( iterable: Iterable<TElement>, private readonly _defaultValue: TElement, ) { this._iterator = iterable[Symbol.iterator](); }
public next(): IteratorResult<TElement> { if (this._done) { return done(this); }
const result = this._iterator.next();
if (!this._started) { this._started = true;
if (result.done) { this._done = true;
return { done: false, value: this._defaultValue }; } else { return result; } } else { return result; } }}
/** * @hidden */export class LazyDistinctIterator<TElement, TKey = TElement> implements Iterator<TElement> { private readonly _iterator: Iterator<TElement>; private readonly _found = new Set<TKey>();
public constructor( iterable: Iterable<TElement>, private readonly _compareOn?: MapFn<TElement, TKey>, ) { this._iterator = iterable[Symbol.iterator](); }
public next(): IteratorResult<TElement> { while (true) { const result = this._iterator.next();
if (result.done) { return done(this); } else { const key: TKey = this._compareOn ? this._compareOn(result.value) : (result.value as any);
if (!this._found.has(key)) { this._found.add(key);
return result; } } } }}
/** * @hidden */export class LazyExceptIterator<TElement, TKey = TElement> implements Iterator<TElement> { private readonly _firstIterator: Iterator<TElement>; private readonly _set = new Set<TKey>();
public constructor( firstIterable: Iterable<TElement>, secondIterable: Iterable<TElement>, private readonly _compareOn?: MapFn<TElement, TKey>, ) { this._firstIterator = firstIterable[Symbol.iterator]();
for (const element of secondIterable) { this._set.add(_compareOn ? _compareOn(element) : (element as any)); } }
public next(): IteratorResult<TElement> { while (true) { const result = this._firstIterator.next();
if (result.done) { return done(this); } else { const key: TKey = this._compareOn ? this._compareOn(result.value) : (result.value as any);
if (!this._set.has(key)) { this._set.add(key);
return result; } } } }}
/** * @hidden */export class LazyGroupByIterator< TSource, TKey, TElement = TSource, TResult = IGrouping<TKey, TElement>> implements Iterator<TResult> { private readonly _elementMapIterator: Iterator<[TKey, TElement[]]>;
public constructor( iterable: Iterable<TSource>, keyFn: MapFn<TSource, TKey>, elementSelector?: MapFn<TSource, TElement>, private readonly _resultSelector?: CombineFn< TKey, Iterable<TElement>, TResult >, ) { const elementMap = new Map<TKey, TElement[]>(); for (const element of iterable) { const key = keyFn(element); const result: TElement = elementSelector ? elementSelector(element) : (element as any);
const arr = elementMap.get(key); if (!arr) { elementMap.set(key, [result]); } else { arr.push(result); } }
this._elementMapIterator = elementMap[Symbol.iterator](); }
public next(): IteratorResult<TResult> { const result = this._elementMapIterator.next();
if (result.done) { return done(this); } else { const element: TResult = this._resultSelector ? this._resultSelector(result.value[0], result.value[1]) : ({ key: result.value[0], elements: result.value[1] } as any);
return { done: false, value: element }; } }}
/** * @hidden */export class LazyGroupJoinIterator<TFirst, TSecond, TKey, TResult> implements Iterator<TResult> { private readonly _firstIterator: Iterator<TFirst>; private readonly _secondMap = new Map<TKey, TSecond[]>();
public constructor( firstIterable: Iterable<TFirst>, secondIterable: Iterable<TSecond>, private readonly _firstKeyFn: MapFn<TFirst, TKey>, secondKeyFn: MapFn<TSecond, TKey>, private readonly _joinFn: CombineFn<TFirst, Iterable<TSecond>, TResult>, ) { this._firstIterator = firstIterable[Symbol.iterator]();
for (const secondElement of secondIterable) { const key = secondKeyFn(secondElement);
const arr = this._secondMap.get(key); if (!arr) { this._secondMap.set(key, [secondElement]); } else { arr.push(secondElement); } } }
public next(): IteratorResult<TResult> { while (true) { const result = this._firstIterator.next();
if (result.done) { return done(this); } else { const key = this._firstKeyFn(result.value); const secondElements = this._secondMap.get(key);
if (secondElements) { return { done: false, value: this._joinFn(result.value, secondElements), }; } } } }}
/** * @hidden */export class LazyIntersectIterator<TElement, TKey = TElement> implements Iterator<TElement> { private readonly _firstIterator: Iterator<TElement>; private readonly _set = new Set<TKey>();
public constructor( firstIterable: Iterable<TElement>, secondIterable: Iterable<TElement>, private readonly _compareOn?: MapFn<TElement, TKey>, ) { this._firstIterator = firstIterable[Symbol.iterator]();
for (const element of secondIterable) { const key: TKey = _compareOn ? _compareOn(element) : (element as any); this._set.add(key); } }
public next(): IteratorResult<TElement> { while (true) { const result = this._firstIterator.next();
if (result.done) { return done(this); } else { const key: TKey = this._compareOn ? this._compareOn(result.value) : (result.value as any);
if (this._set.has(key)) { this._set.delete(key);
return result; } } } }}
/** * @hidden */export class LazyJoinIterator<TFirst, TSecond, TKey, TResult> implements Iterator<TResult> { private readonly _firstIterator: Iterator<TFirst>; private readonly _secondMap: Map<TKey, TSecond>;
public constructor( firstIterable: Iterable<TFirst>, secondIterable: Iterable<TSecond>, private readonly _firstKeyFn: MapFn<TFirst, TKey>, secondKeyFn: MapFn<TSecond, TKey>, private readonly _joinFn: CombineFn<TFirst, TSecond, TResult>, ) { this._firstIterator = firstIterable[Symbol.iterator](); this._secondMap = toMap(secondIterable, secondKeyFn); }
public next(): IteratorResult<TResult> { while (true) { const result = this._firstIterator.next();
if (result.done) { return done(this); } else { const key = this._firstKeyFn(result.value); const secondElement = this._secondMap.get(key);
if (secondElement) { return { done: false, value: this._joinFn(result.value, secondElement), }; } } } }}
/** * Attempts to mimic the built-in sorting as close as possible. * @hidden */function defaultComparer<T>(a: T, b: T): number { // eslint-disable-next-line @typescript-eslint/restrict-plus-operands return ('' + a).localeCompare('' + b);}
/** * @hidden */export function numericComparer(a: number, b: number): number { return a - b;}
/** * @hidden */function comparerFactory<TSource, TKey>( keyFn: MapFn<TSource, TKey>, reverse: boolean, compareFn: SortFn<TKey> = defaultComparer,): (a: TSource, b: TSource) => number { return (a, b) => { if (reverse) { const t = a; a = b; b = t; } return compareFn(keyFn(a), keyFn(b)); };}
/** * @hidden */export function lazyOrderBy<TElement, TKey>( iterable: Iterable<TElement>, keyFn: MapFn<TElement, TKey>, compareFn: SortFn<TKey> | undefined, decending: boolean,): Iterator<TElement> { const arr = toArray(iterable); arr.sort(comparerFactory(keyFn, decending, compareFn)); return arr[Symbol.iterator]();}
/** * @hidden */export class LazyReverseIterator<TElement> implements Iterator<TElement> { private readonly _arr: TElement[]; private _index: number;
public constructor(iterable: Iterable<TElement>) { this._arr = toArray(iterable); this._index = this._arr.length - 1; }
public next(): IteratorResult<TElement> { if (this._index < 0) { return done(this); } else { const nextResult = { done: false, value: this._arr[this._index], } as const; this._index--; return nextResult; } }}
/** * @hidden */export class LazySelectIterator<TSource, TResult> implements Iterator<TResult> { private readonly _iterator: Iterator<TSource>; private _index = 0;
public constructor( iterable: Iterable<TSource>, private readonly _selector: IndexMapFn<TSource, TResult>, ) { this._iterator = iterable[Symbol.iterator](); }
public next(): IteratorResult<TResult> { const result = this._iterator.next();
if (result.done) { return done(this); } else { const nextResult = { done: false, value: this._selector(result.value, this._index), }; this._index++;
return nextResult; } }}
/** * @hidden */export class LazySelectManyIterator<TSource, TResult> implements Iterator<TResult> { private readonly _iterator: Iterator<TSource>; private _internalIterator: Iterator<TResult> | undefined; private _index = 0;
public constructor( iterable: Iterable<TSource>, private readonly _selector: IndexMapFn<TSource, Iterable<TResult>>, ) { this._iterator = iterable[Symbol.iterator](); }
public next(): IteratorResult<TResult> { while (true) { if (!this._internalIterator) { const result = this._iterator.next();
if (result.done) { return done(this); } else { const element = this._selector(result.value, this._index); this._index++;
this._internalIterator = element[Symbol.iterator](); } }
const internalResult = this._internalIterator.next();
if (internalResult.done) { this._internalIterator = undefined; } else { return internalResult; } } }}
/** * @hidden */export class LazySkipIterator<TElement> implements Iterator<TElement> { private readonly _iterator: Iterator<TElement>; private _skipped = 0;
public constructor( iterable: Iterable<TElement>, private readonly _count: number, ) { this._iterator = iterable[Symbol.iterator](); }
public next(): IteratorResult<TElement> { while (true) { const result = this._iterator.next();
if (result.done) { return done(this); } else { if (this._skipped < this._count) { this._skipped++; } else { return result; } } } }}
/** * @hidden */export class LazySkipLastIterator<TElement> implements Iterator<TElement> { private readonly _iterator: Iterator<TElement>; private readonly _queue = new Queue<TElement>();
public constructor( iterable: Iterable<TElement>, private readonly _count: number, ) { this._iterator = iterable[Symbol.iterator](); }
public next(): IteratorResult<TElement> { while (true) { const result = this._iterator.next();
if (result.done) { return done(this); } else { this._queue.enqueue(result.value);
if (this._queue.length > this._count) { return { done: false, value: this._queue.dequeue() }; } } } }}
/** * @hidden */export class LazySkipWhile<TElement> implements Iterator<TElement> { private readonly _iterator: Iterator<TElement>; private _index = 0; private _yielding = false;
public constructor( iterable: Iterable<TElement>, private readonly _predicate: IndexPredicate<TElement>, ) { this._iterator = iterable[Symbol.iterator](); }
public next(): IteratorResult<TElement> { while (true) { const result = this._iterator.next();
if (result.done) { return done(this); } else { if (!this._yielding) { this._yielding = !this._predicate(result.value, this._index); this._index++; }
if (this._yielding) { return result; } } } }}
/** * @hidden */export class LazyTakeIterator<TElement> implements Iterator<TElement> { private readonly _iterator: Iterator<TElement>; private _taken = 0;
public constructor( iterable: Iterable<TElement>, private readonly _count: number, ) { this._iterator = iterable[Symbol.iterator](); }
public next(): IteratorResult<TElement> { if (this._taken >= this._count) { return done(this); }
const result = this._iterator.next(); this._taken++;
if (result.done) { return done(this); } else { return result; } }}
/** * @hidden */export class LazyTakeLastIterator<TElement> implements Iterator<TElement> { private _queue = new Queue<TElement>();
public constructor(iterable: Iterable<TElement>, count: number) { if (count > 0) { for (const element of iterable) { if (this._queue.length < count) { this._queue.enqueue(element); } else { this._queue.dequeue(); this._queue.enqueue(element); } } } }
public next(): IteratorResult<TElement> { if (this._queue.length === 0) { return done(this); } else { return { done: false, value: this._queue.dequeue() }; } }}
/** * @hidden */export class LazyTakeWhileIterator<TElement> implements Iterator<TElement> { private readonly _iterator: Iterator<TElement>; private _index = 0;
public constructor( iterable: Iterable<TElement>, private readonly _predicate: IndexPredicate<TElement>, ) { this._iterator = iterable[Symbol.iterator](); }
public next(): IteratorResult<TElement> { const result = this._iterator.next();
if (result.done) { return done(this); } else { if (!this._predicate(result.value, this._index)) { return done(this); } else { this._index++;
return result; } } }}
/** * @hidden */export class LazyUnionIterator<TElement, TKey = TElement> implements Iterator<TElement> { private readonly _firstIterator: Iterator<TElement>; private readonly _secondIterator: Iterator<TElement>; private readonly _set = new Set<TKey>(); private _onSecond = false;
public constructor( firstIterable: Iterable<TElement>, secondIterable: Iterable<TElement>, private readonly _compareOn?: MapFn<TElement, TKey>, ) { this._firstIterator = firstIterable[Symbol.iterator](); this._secondIterator = secondIterable[Symbol.iterator](); }
public next(): IteratorResult<TElement> { while (true) { const result = this._onSecond ? this._secondIterator.next() : this._firstIterator.next();
if (result.done) { if (this._onSecond) { return done(this); } else { this._onSecond = true; } } else { const key: TKey = this._compareOn ? this._compareOn(result.value) : (result.value as any);
if (!this._set.has(key)) { this._set.add(key);
return { done: false, value: result.value }; } } } }}
/** * @hidden */export class LazyWhereIterator<TElement> implements Iterator<TElement> { private readonly _iterator: Iterator<TElement>; private _index = 0;
public constructor( iterable: Iterable<TElement>, private readonly _predicate: IndexPredicate<TElement>, ) { this._iterator = iterable[Symbol.iterator](); }
public next(): IteratorResult<TElement> { while (true) { const result = this._iterator.next();
if (result.done) { return done(this); } else { const shouldYield = this._predicate(result.value, this._index); this._index++;
if (shouldYield) { return { done: false, value: result.value }; } } } }}
/** * @hidden */export class LazyZipIterator<TFirst, TSecond, TResult = [TFirst, TSecond]> implements Iterator<TResult> { private readonly _firstIterator: Iterator<TFirst>; private readonly _secondIterator: Iterator<TSecond>;
public constructor( firstIterable: Iterable<TFirst>, secondIterable: Iterable<TSecond>, private readonly _selector?: CombineFn<TFirst, TSecond, TResult>, ) { this._firstIterator = firstIterable[Symbol.iterator](); this._secondIterator = secondIterable[Symbol.iterator](); }
public next(): IteratorResult<TResult> { const firstResult = this._firstIterator.next();
if (firstResult.done) { return done(this); }
const secondResult = this._secondIterator.next();
if (secondResult.done) { return done(this); }
return { done: false, value: this._selector ? this._selector(firstResult.value, secondResult.value) : ([firstResult.value, secondResult.value] as any), }; }}