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