enumerable-ts/src/index.ts
2025-04-30 21:41:23 -05:00

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)
}
}
}