const DoneIteratorResult = Object.freeze({ value: undefined, done: true }) type Nullable = T | null | undefined export interface Predicate { (value: Nullable, index: number): boolean } export interface Reducer { (accumulator: U, value: Nullable, index: number): U } export interface Morphism { (value: Nullable, index: number): U } export interface IEnumerator { get current(): Nullable moveNext(): boolean reset(): void toIterator(): Iterator } export interface IEnumerable { enumerator(): IEnumerator } export interface IEnumeratorFactory { (iterator: Iterator): IEnumerator } function hasMethods(methods: PropertyKey[], obj: object): boolean { return methods.every(method => typeof obj[method] === 'function') } export function isIterable(value: any): value is Iterable { return typeof value[Symbol.iterator] === 'function' } export function isIterator(value: any): value is Iterator { return typeof value.next === 'function' } export function isBuffer(value: any): value is ArrayBufferLike { return ArrayBuffer.isView(value) } export function isArrayLike(value: any): value is ArrayLike { return Array.isArray(value) || value instanceof Array } export class IteratorEnumerator implements IEnumerator, Iterator { private _iterator: Iterator private _consumed: boolean = false private _current: Nullable get current(): Nullable { return this._current } constructor(iterator: Iterator) { this._iterator = iterator } static from(iterator: Iterator): IteratorEnumerator { return new IteratorEnumerator(iterator) } moveNext(): boolean { if (!this._consumed) { const { value, done } = this._iterator.next() this._current = value if (done) { this._consumed = true } return !done } else { return false } } reset(): void { } toIterator(): Iterator { return this as Iterator } next(...[_value]: [] | [any]): IteratorResult { const done = this.moveNext() return { value: this.current, done } as IteratorResult } return(value?: any): IteratorResult { return this._iterator.return?.(value) ?? DoneIteratorResult } throw(e?: any): IteratorResult { return this._iterator.throw?.(e) ?? DoneIteratorResult } } export class CachedIteratorEnumerator implements IEnumerator, Iterator { private _iterator: IteratorEnumerator private _cache: Nullable[] = [] private _index: number = -1 get current(): Nullable { return this._cache[this._index] } constructor(iterator: Iterator) { this._iterator = new IteratorEnumerator(iterator) } static from(iterator: Iterator): CachedIteratorEnumerator { return new CachedIteratorEnumerator(iterator) } moveNext(): boolean { this._index += 1 if (this._cache.length > this._index) { return true } else if (this._iterator.moveNext()) { this._cache.push(this._iterator.current) return true } else { return false } } reset(): void { this._index = -1 } toIterator(): Iterator { return this as Iterator } next(...[_value]: [] | [any]): IteratorResult { const done = this.moveNext() return { value: this.current, done } as IteratorResult } return?(value?: any): IteratorResult { return this._iterator.return(value) } throw(e?: any): IteratorResult { return this._iterator.throw(e) } } export class IterableEnumerator implements IEnumerator, Iterator { private _iterable: Iterable private _factory: IEnumeratorFactory private _enumerator: Nullable> get current(): Nullable { return this._enumerator?.current } constructor(iterable: Iterable, factory: IEnumeratorFactory = IteratorEnumerator.from) { this._iterable = iterable this._factory = factory this._enumerator = this._createEnumerator() } static fromIterable(iterable: Iterable, factory?: IEnumeratorFactory): IEnumerator { return new this(iterable, factory) } moveNext(): boolean { return this._enumerator?.moveNext() ?? false } _createIterator(): Iterator { return this._iterable[Symbol.iterator]() } _createEnumerator(): IEnumerator { return this._factory(this._createIterator()) } reset(): void { this._enumerator = this._createEnumerator() } toIterator(): Iterator { return this._createIterator() as Iterator } next(...[_value]: [] | [any]): IteratorResult { const done = !this.moveNext() return { value: this.current, done } as IteratorResult } return?(value?: any): IteratorResult { if (isIterator(this._enumerator)) { return this._enumerator?.return?.(value) || DoneIteratorResult } else { return DoneIteratorResult } } throw?(e?: any): IteratorResult { if (isIterator(this._enumerator)) { return this._enumerator?.throw?.(e) || DoneIteratorResult } else { return DoneIteratorResult } } } // ugly hack to stamp arraybuffers to be arraylike function toArrayLikeBuffer(buffer: T): T & ArrayLike { return Object.defineProperty(buffer, 'length', { get: function() { return buffer.byteLength } }) as T & ArrayLike } type ArrayType | ArrayBufferView> = T extends ArrayLike ? U : T extends ArrayBufferTypes ? number : never export class ArrayEnumerator implements IEnumerator, Iterator, Iterable { private _array: ArrayLike private _index: number = -1 get current(): Nullable { return this._array[this._index] } constructor(array: ArrayLike) { this._array = array } static from | ArrayBufferView>(array: A): ArrayEnumerator> { if (ArrayBuffer.isView(array)) { return new ArrayEnumerator(toArrayLikeBuffer(array)) } else { return new ArrayEnumerator(array) } } [Symbol.iterator](): Iterator { return this._array[Symbol.iterator]() } setIndex(index: number) { this._index = index } moveNext(): boolean { this._index += 1 return this._index < this._array.length } reset(): void { this._index = -1 } toIterator(): Iterator { return this._array[Symbol.iterator]() } next(...[_value]: [] | [any]): IteratorResult { const done = this.moveNext() return { value: this.current, done } as IteratorResult } return?(_value?: any): IteratorResult { return DoneIteratorResult } throw?(_e?: any): IteratorResult { return DoneIteratorResult } } export class Enumerator implements IEnumerator, Iterator { protected _enumerator: IEnumerator protected _index: number = 0 get current(): Nullable { return this._enumerator.current } constructor(enumerator: IEnumerator) { this._enumerator = enumerator } static fromIterable(iterable: Iterable): IEnumerator { if (isArrayLike(iterable) || ArrayBuffer.isView(iterable)) { return ArrayEnumerator.from(iterable) } else { return new this(new IterableEnumerator(iterable)) } } static fromIterator(iterator: Iterator, cache: boolean = true): IEnumerator { if (cache) { return new CachedIteratorEnumerator(iterator) } else { return new IteratorEnumerator(iterator) } } static toIterator(enumerator: IEnumerator): Iterator { return new this(enumerator) } [Symbol.iterator]() { return this.toIterator() } toIterator(): Iterator { return Enumerator.toIterator(this) as Iterator } moveNext(): boolean { this._index += 1 return this._enumerator.moveNext() } reset(): void { this._enumerator.reset() } next(...[_value]: [] | [any]): IteratorResult { const done = this.moveNext() return { value: this.current, done } as IteratorResult } return?(_value?: any): IteratorResult { return DoneIteratorResult } throw?(_e?: any): IteratorResult { return DoneIteratorResult } } export class HelperEnumerator implements IEnumerator, Iterator { protected _enumerator: IEnumerator protected _index: number = 0 get current(): Nullable { return this._enumerator.current } constructor(enumerator: IEnumerator) { this._enumerator = enumerator } static toIterator(enumerator: IEnumerator): Iterator { return new this(enumerator) } [Symbol.iterator]() { return this.toIterator() } toIterator(): Iterator { return HelperEnumerator.toIterator(this) as Iterator } moveNext(): boolean { this._index += 1 return this._enumerator.moveNext() } reset(): void { this._enumerator.reset() } next(...[_value]: [] | [any]): IteratorResult { const done = this.moveNext() return { value: this.current, done } as IteratorResult } return?(_value?: any): IteratorResult { return DoneIteratorResult } throw?(_e?: any): IteratorResult { return DoneIteratorResult } } export class DropEnumerator extends HelperEnumerator { private _limit: number constructor(enumerator: IEnumerator, limit: number) { super(enumerator) this._limit = limit } moveNext(): boolean { let next = super.moveNext() while (this._limit > 0 && next) { next = super.moveNext() this._limit -= 1 } return next } } export class FilterEnumerator extends HelperEnumerator { private _filter: Predicate constructor(enumerator: IEnumerator, filter: Predicate) { super(enumerator) this._filter = filter } moveNext(): boolean { let next = super.moveNext() while (next && !this._filter(this.current, this._index)) { next = super.moveNext() } return next } } export class FlatMapEnumerator implements IEnumerator { private _enumerator: IEnumerator private _flatMap: Morphism> private _inner?: IEnumerator private _index: number = -1 constructor(enumerator: IEnumerator, flatMap: Morphism>) { this._enumerator = enumerator this._flatMap = flatMap } get current(): Nullable { return this._inner?.current } moveNext(): boolean { if (this._inner && this._inner.moveNext()) { return true } const next = this._enumerator.moveNext() if (!next) { return false } this._index += 1 this._inner = this._flatMap(this._enumerator.current, this._index) return this._inner.moveNext() } reset(): void { this._index = -1 this._inner = undefined this._enumerator.reset() } toIterator(): Iterator { return HelperEnumerator.toIterator(this) as Iterator } } export class MapEnumerator implements IEnumerator { private _enumerator: IEnumerator private _map: Morphism private _current!: U private _index: number = -1 get current(): U { return this._current } constructor(enumerator: IEnumerator, map: Morphism) { this._enumerator = enumerator this._map = map } toIterator(): Iterator { return HelperEnumerator.toIterator(this) as Iterator } moveNext(): boolean { this._index += 1 const next = this._enumerator.moveNext() if (next) { this._current = this._map(this._enumerator.current, this._index) } return next } reset(): void { this._index = -1 this._enumerator.reset() } } export class TakeEnumerator extends HelperEnumerator { private _limit: number constructor(enumerator: IEnumerator, limit: number) { super(enumerator) this._limit = limit } moveNext(): boolean { if (this._limit < 0) { return false } else { super.moveNext() this._limit -= 1 return true } } } export class FusedEnumerator implements IEnumerator { private _enumerators: IEnumerator[] private _index: number = 0 get current(): Nullable { return this._cur()?.current } constructor(enumerators: IEnumerator[]) { this._enumerators = enumerators } private _cur() { return this._enumerators[this._index] } private _done() { return this._index >= this._enumerators.length } toIterator(): Iterator { return HelperEnumerator.toIterator(this) as Iterator } moveNext(): boolean { while (!this._done() && !this._cur().moveNext()) { this._index += 1 } return this._done() } reset(): void { const len = this._enumerators.length for (let i = 0; i < len; i++) { this._enumerators[i].reset() } this._index = 0 } } export class Enumerable implements IEnumerable, Iterable { protected _enumerator: IEnumerator constructor(enumerator: IEnumerator) { this._enumerator = enumerator } static from(value: IEnumerable | IEnumerator | Iterable | Iterator): Enumerable { if (this.isEnumerable(value)) { return value } else if (this.isEnumerator(value)) { return new Enumerable(value) } else if (isIterable(value)) { const enumerator = Enumerator.fromIterable(value) if (isArrayLike(value)) { return new ArrayEnumerble(enumerator) } else { return new Enumerable(enumerator) } } else if (isIterator(value)) { return new Enumerable( Enumerator.fromIterator(value) ) } else { throw new TypeError('value is not enumerable') } } static isEnumerable(value: object): value is Enumerable { return typeof value['enumerator'] === 'function' } static isEnumerator(value: object): value is IEnumerator { return hasMethods(['moveNext', 'reset', 'toIterator'], value) } [Symbol.iterator](): Iterator { return this._enumerator.toIterator() } at(index: number): Nullable { while (index >= 0 && this._enumerator.moveNext()) { index -= 1 } const value = this._enumerator.current this._enumerator.reset() return value } atOrDefault(index: number, defaultValue: T) { const value = this.at(index) return value || defaultValue } atOrElse(index: number, defaultValue: () => T) { const value = this.at(index) return value || defaultValue() } concat(other: IEnumerable): IEnumerable { return new Enumerable( new FusedEnumerator([this._enumerator, other.enumerator()]) ) } drop(limit: number) { return new Enumerable(new DropEnumerator(this._enumerator, limit)) } enumerator(): IEnumerator { return this._enumerator } //entries(): IEnumerator { //} every(predicate: Predicate): boolean { let index = 0 while (this._enumerator.moveNext()) { if (!predicate(this._enumerator.current, index)) { return false } index += 1 } this._enumerator.reset() return true } filter(predicate: Predicate): IEnumerable { return new Enumerable(new FilterEnumerator(this._enumerator, predicate)) } flatMap(fn: Morphism>): IEnumerable { return new Enumerable(new FlatMapEnumerator(this._enumerator, fn)) } map(fn: Morphism): IEnumerable { return new Enumerable(new MapEnumerator(this._enumerator, fn)) } some(predicate: Predicate): boolean { let index = 0 while (this._enumerator.moveNext()) { if (predicate(this._enumerator.current, index)) { return true } index += 1 } this._enumerator.reset() return false } take(limit: number): IEnumerable { return new Enumerable(new TakeEnumerator(this._enumerator, limit)) } } export class ArrayEnumerble extends Enumerable { at(index: number): Nullable { if (this._enumerator instanceof ArrayEnumerator) { this._enumerator.setIndex(index) return this._enumerator.current } else { return super.at(index) } } }