commit a172e6a50fe6c0cc232f7603eb857dee3e364c2d Author: Hervé BECHER Date: Sat May 4 01:19:44 2024 +0200 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..30bc162 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/node_modules \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..77e1bef --- /dev/null +++ b/package-lock.json @@ -0,0 +1,213 @@ +{ + "name": "enumerable", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "enumerable", + "version": "1.0.0", + "license": "ISC", + "devDependencies": { + "@types/node": "^20.12.7", + "ts-node": "^10.9.2", + "typescript": "^5.4.5" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", + "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", + "dev": true + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true + }, + "node_modules/@types/node": { + "version": "20.12.8", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.8.tgz", + "integrity": "sha512-NU0rJLJnshZWdE/097cdCBbyW1h4hEg0xpovcoAQYHl8dnEyp/NAOiE45pvc+Bd1Dt+2r94v2eGFpQJ4R7g+2w==", + "dev": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/acorn": { + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", + "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/typescript": { + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", + "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "engines": { + "node": ">=6" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..67fd29e --- /dev/null +++ b/package.json @@ -0,0 +1,18 @@ +{ + "name": "enumerable", + "version": "1.0.0", + "description": "Implementation of Enumerable API from .NET", + "main": "src/index.ts", + "type": "module", + "scripts": { + "start": "node --import ./ts-loader.js src/index.ts", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "Hervé BECHER", + "license": "ISC", + "devDependencies": { + "@types/node": "^20.12.7", + "ts-node": "^10.9.2", + "typescript": "^5.4.5" + } +} diff --git a/src/async.ts b/src/async.ts new file mode 100644 index 0000000..fdddfd6 --- /dev/null +++ b/src/async.ts @@ -0,0 +1,2093 @@ +import { Accumulator, Action, BiConverter, Comparer, Converter, Enumerable, Equater, Predicate, wrap as wrapSync } from "./sync.js"; +import { AsyncFunction, MaybeAsyncFunction, MaybePromise, asAsyncGenerator, combineAsyncComparers, defaultArrayComparer, identity, isAsyncIterable, operatorCompare, strictEquals } from "./utils.js"; +import { createQueue } from "./queue.js"; +import { selectionSorter } from "./sorting.js"; +import { createAsyncEqualitySet } from "./equality-set.js"; +import { createAsyncEqualityMap } from "./equality-map.js"; + +//#region types + +export type MaybeAsyncIterable = Iterable | AsyncIterable; +export type MaybeAsyncIterator = Iterator | AsyncIterator; + +export type AsyncPredicate = AsyncFunction>; +export type MaybeAsyncPredicate = MaybeAsyncFunction>; +export type AsyncConverter = AsyncFunction>; +export type MaybeAsyncConverter = MaybeAsyncFunction>; +export type AsyncBiConverter = AsyncFunction>; +export type MaybeAsyncBiConverter = MaybeAsyncFunction>; +export type AsyncAction = AsyncFunction>; +export type MaybeAsyncAction = MaybeAsyncFunction>; +export type AsyncAccumulator = AsyncFunction>; +export type MaybeAsyncAccumulator = MaybeAsyncFunction>; +export type AsyncComparer = AsyncFunction>; +export type MaybeAsyncComparer = MaybeAsyncFunction>; +export type AsyncEquater = AsyncFunction>; +export type MaybeAsyncEquater = MaybeAsyncFunction>; + +//#endregion + +//#region interfaces + +export interface AsyncEnumerable extends AsyncIterable { + iterator(): AsyncIterator; + + apply(pipeline: (enumerable: this) => TResult): TResult; + + count(predicate?: MaybeAsyncPredicate): Promise; + nonEnumeratedCount(): Promise; + fastCount(): Promise; + + select(selector: MaybeAsyncConverter): AsyncEnumerable; + selectMany(selector: MaybeAsyncConverter>): AsyncEnumerable; + + where(predicate: MaybeAsyncPredicate): AsyncEnumerable; + + groupBy(keySelector: MaybeAsyncConverter, elementSelector?: undefined, keyComparer?: MaybeAsyncEquater): AsyncEnumerable>; + groupBy(keySelector: MaybeAsyncConverter, elementSelector: MaybeAsyncConverter, keyComparer?: MaybeAsyncEquater): AsyncEnumerable>; + + join(iterable: MaybeAsyncIterable, firstKeySelector: MaybeAsyncConverter, secondKeySelector: MaybeAsyncConverter, resultSelector?: undefined, keyComparer?: MaybeAsyncEquater): AsyncEnumerable<[TElement, TOther]>; + join(iterable: MaybeAsyncIterable, firstKeySelector: MaybeAsyncConverter, secondKeySelector: MaybeAsyncConverter, resultSelector: MaybeAsyncBiConverter, keyComparer?: MaybeAsyncEquater): AsyncEnumerable; + + groupJoin(iterable: MaybeAsyncIterable, firstKeySelector: MaybeAsyncConverter, secondKeySelector: MaybeAsyncConverter, resultSelector?: undefined, keyComparer?: MaybeAsyncEquater): AsyncEnumerable>; + groupJoin(iterable: MaybeAsyncIterable, firstKeySelector: MaybeAsyncConverter, secondKeySelector: MaybeAsyncConverter, resultSelector: MaybeAsyncBiConverter, TResult>, keyComparer?: MaybeAsyncEquater): AsyncEnumerable; + + contains(obj: TElement, equater?: MaybeAsyncEquater): Promise; + + sequenceEqual(iterable: MaybeAsyncIterable, equater?: MaybeAsyncEquater): Promise; + + append(obj: TElement): AsyncEnumerable; + + prepend(obj: TElement): AsyncEnumerable; + + remove(obj: TElement, all?: boolean, equater?: MaybeAsyncEquater): AsyncEnumerable; + + concat(...iterables: MaybeAsyncIterable[]): AsyncEnumerable; + + first(predicate?: MaybeAsyncPredicate): Promise; + firstOrDefault(predicate?: MaybeAsyncPredicate, def?: TElement): Promise; + + last(predicate?: MaybeAsyncPredicate): Promise; + lastOrDefault(predicate?: MaybeAsyncPredicate, def?: TElement): Promise; + + single(predicate?: MaybeAsyncPredicate): Promise; + singleOrDefault(predicate?: MaybeAsyncPredicate, def?: TElement): Promise; + + elementAt(index: number): Promise; + elementAtOrDefault(index: number, def?: TElement): Promise; + + aggregate(accumulator: MaybeAsyncAccumulator): Promise; + aggregate(accumulator: MaybeAsyncAccumulator, seed?: TAccumulator): Promise; + aggregate(accumulator: MaybeAsyncAccumulator, seed?: TAccumulator, resultSelector?: MaybeAsyncConverter): Promise; + + min(): Promise; + minBy(selector: MaybeAsyncConverter): Promise; + + max(): Promise; + maxBy(selector: MaybeAsyncConverter): Promise; + + order(comparer?: MaybeAsyncComparer): AsyncEnumerable; + orderBy(selector: MaybeAsyncConverter, comparer?: MaybeAsyncComparer): AsyncEnumerable; + + orderDescending(comparer?: MaybeAsyncComparer): AsyncEnumerable; + orderByDescending(selector: MaybeAsyncConverter, comparer?: MaybeAsyncComparer): AsyncEnumerable; + + distinct(equater?: MaybeAsyncEquater): AsyncEnumerable; + distinctBy(selector: MaybeAsyncConverter, equater?: MaybeAsyncEquater): AsyncEnumerable; + + union(iterable: MaybeAsyncIterable, equater?: MaybeAsyncEquater): AsyncEnumerable; + unionBy(iterable: MaybeAsyncIterable, selector: MaybeAsyncConverter, equater?: MaybeAsyncEquater): AsyncEnumerable; + + except(iterable: MaybeAsyncIterable, equater?: MaybeAsyncEquater): AsyncEnumerable; + exceptBy(iterable: MaybeAsyncIterable, selector: MaybeAsyncConverter, equater?: MaybeAsyncEquater): AsyncEnumerable; + + intersect(iterable: MaybeAsyncIterable, equater?: MaybeAsyncEquater): AsyncEnumerable; + intersectBy(iterable: MaybeAsyncIterable, selector: MaybeAsyncConverter, equater?: MaybeAsyncEquater): AsyncEnumerable; + + all(predicate: MaybeAsyncPredicate): Promise; + any(predicate: MaybeAsyncPredicate): Promise; + any(): Promise; + + skip(n: number): AsyncEnumerable; + skipLast(n: number): AsyncEnumerable; + skipWhile(condition: MaybeAsyncPredicate): AsyncEnumerable; + + take(n: number): AsyncEnumerable; + takeLast(n: number): AsyncEnumerable; + takeWhile(condition: MaybeAsyncPredicate): AsyncEnumerable; + + peek(action: MaybeAsyncAction): AsyncEnumerable; + + forEach(action: MaybeAsyncAction): Promise; + + zip(iterable: MaybeAsyncIterable): AsyncEnumerable<[TElement, TOther]>; + + index(): AsyncEnumerable<[number, TElement]>; + + reverse(): AsyncEnumerable; + + chunk(size: number): AsyncEnumerable; + + cache(): AsyncEnumerable; + + toArray(): Promise; + toMap(keySelector: MaybeAsyncConverter, valueSelector: MaybeAsyncConverter): Promise>; + toSet(): Promise>; + toObject(keySelector: MaybeAsyncConverter, valueSelector: MaybeAsyncConverter): Promise>; +} + +export interface GroupedAsyncEnumerable extends AsyncEnumerable { + get key(): TKey; +} + +export interface OrderedAsyncEnumerable extends AsyncEnumerable { + get comparer(): MaybeAsyncComparer; + + thenSelf(comparer?: MaybeAsyncComparer): OrderedAsyncEnumerable; + + thenBy(selector: MaybeAsyncConverter, comparer?: MaybeAsyncComparer): OrderedAsyncEnumerable; + + thenSelfDescending(comparer?: MaybeAsyncComparer): OrderedAsyncEnumerable; + + thenByDescending(selector: MaybeAsyncConverter, comparer?: MaybeAsyncComparer): OrderedAsyncEnumerable; +} + +//#endregion + +//#region wrappers + +export function asAsync(enumerable: Enumerable): AsyncEnumerable { + return new WrappedEnumerable(enumerable); +} + +export function wrap(iterable: MaybeAsyncIterable): AsyncEnumerable { + if (iterable instanceof BaseAsyncEnumerable) { + return iterable; + } + + if (isAsyncIterable(iterable)) { + return sequence(iterable); + } + + return asAsync(wrapSync(iterable)); +} + +export function sequence(iterable: AsyncIterable): AsyncEnumerable { + return new WrappedAsyncIterable(iterable); +} + +export function empty(): AsyncEnumerable { + return EmptyAsyncEnumerable.INSTANCE; +} + +export function single(obj: T): AsyncEnumerable { + return new WrappedObjectAsync(obj); +} + +export function array(array: T[]): AsyncEnumerable { + return new WrappedArrayAsync(array); +} + +export function arrayLike(arrayLike: ArrayLike): AsyncEnumerable { + return new WrappedArrayLikeAsync(arrayLike); +} + +export function of(...elements: T[]): AsyncEnumerable { + switch (elements.length) { + case 0: + return empty(); + case 1: + return single(elements[0]); + default: + return array(elements); + } +} + +export function func(f: () => Promise): AsyncEnumerable { + return new FunctionAsyncEnumerable(f); +} + +export function generator(generator: () => AsyncGenerator): AsyncEnumerable { + return new GeneratorAsyncEnumerable(generator); +} + +export function range(max: number): AsyncEnumerable +export function range(min: number, max: number): AsyncEnumerable +export function range(min: number, max: number, step: number): AsyncEnumerable +export function range(a: number, b?: number, c?: number): AsyncEnumerable { + if (b === undefined) { + b = a; + a = 0; + } + + if (c === undefined) { + c = 1; + } + + return new RangeAsyncEnumerable(a, b, c); +} + +export function repeat(value: T, count?: number): AsyncEnumerable { + if (count == undefined) { + return new RepeatForeverAsyncEnumerable(value); + } + + if (count < 0) { + throw new RangeError(); + } + + if (count === 0) { + return empty(); + } + + if (count === 1) { + return new WrappedObjectAsync(value); + } + + return new RepeatAsyncEnumerable(value, count); +} + +export namespace sum { + export async function number(iterable: AsyncIterable) { + let result = 0; + + for await (const n of iterable) { + result += n; + } + + return result; + } + + export async function bigint(iterable: AsyncIterable) { + let result = 0n; + + for await (const n of iterable) { + result += n; + } + + return result; + } +} + +//#endregion + +//#region implementations + +abstract class BaseAsyncEnumerable implements AsyncEnumerable { + [Symbol.asyncIterator]() { + return this.iterator(); + } + + abstract iterator(): AsyncIterator; + + apply(pipeline: (enumerable: this) => TResult) { + return pipeline(this); + } + + select(converter: MaybeAsyncConverter): AsyncEnumerable { + return new MapperAsyncEnumerable(this, converter); + } + + selectMany(converter: MaybeAsyncConverter>): AsyncEnumerable { + return new FlatMapperAsyncEnumerable(this, converter); + } + + where(predicate: MaybeAsyncPredicate): AsyncEnumerable { + return new FilterAsyncEnumerable(this, predicate); + } + + groupBy(keySelector: MaybeAsyncConverter, elementSelector?: MaybeAsyncConverter, keyComparer?: MaybeAsyncEquater): AsyncEnumerable> { + return new GroupByAsyncEnumerable(this, keySelector, elementSelector, keyComparer); + } + + join(iterable: MaybeAsyncIterable, firstKeySelector: MaybeAsyncConverter, secondKeySelector: MaybeAsyncConverter, resultSelector?: MaybeAsyncBiConverter, keyComparer?: MaybeAsyncEquater): AsyncEnumerable { + return new JoinAsyncEnumerable(this, wrap(iterable), firstKeySelector, secondKeySelector, resultSelector, keyComparer); + } + + groupJoin(iterable: MaybeAsyncIterable, firstKeySelector: MaybeAsyncConverter, secondKeySelector: MaybeAsyncConverter, resultSelector?: MaybeAsyncBiConverter, TResult>, keyComparer?: MaybeAsyncEquater): AsyncEnumerable { + return new GroupJoinAsyncEnumerable(this, wrap(iterable), firstKeySelector, secondKeySelector, resultSelector, keyComparer); + } + + async contains(obj: TElement, equater?: MaybeAsyncEquater) { + if (!equater) { + equater = strictEquals; + } + + for await (const element of this) { + if (await equater(element, obj)) { + return true; + } + } + + return false; + } + + async sequenceEqual(iterable: MaybeAsyncIterable, equater?: MaybeAsyncEquater) { + if (this === iterable) { + return true; + } + + const that = wrap(iterable); + + const thisCount = await this.nonEnumeratedCount(); + const thatCount = await that.nonEnumeratedCount(); + + if (thisCount >= 0 && thatCount >= 0 && thisCount !== thatCount) { + return false; + } + + if (!equater) { + equater = strictEquals; + } + + const thisIterator = this.iterator(); + const thatIterator = that.iterator(); + + while (true) { + const thisNext = await thisIterator.next(); + const thatNext = await thatIterator.next(); + + if (thisNext.done) { + return thatNext.done === true; // no undefined! + } + + if (thatNext.done) { + return false; + } + + if (!await equater(thisNext.value, thatNext.value)) { + return false; + } + } + } + + append(obj: TElement): AsyncEnumerable { + return new AppendAsyncEnumerable(this, obj); + } + + prepend(obj: TElement): AsyncEnumerable { + return new PrependAsyncEnumerable(this, obj); + } + + remove(obj: TElement, all?: boolean, equater?: MaybeAsyncEquater): AsyncEnumerable { + return new RemoveAsyncEnumerable(this, obj, all, equater); + } + + concat(...iterables: MaybeAsyncIterable[]): AsyncEnumerable { + if (iterables.length === 0) { + return this; + } + + const arr = new Array>(); + arr.push(this); + + for (const iterable of iterables) { + arr.push(wrap(iterable)); + } + + return new ConcatAsyncEnumerable(arr); + } + + async count(predicate?: MaybeAsyncPredicate) { + let count = 0; + + if (predicate) { + for await (const element of this) { + if (await predicate(element)) { + count++; + } + } + } else { + const iterator = this.iterator(); + + while (!(await iterator.next()).done) { + count++; + } + } + + return count; + } + + async nonEnumeratedCount() { + return -1; + } + + async fastCount() { + const n = await this.nonEnumeratedCount(); + return n >= 0 ? n : await this.count(); + } + + async #tryGetFirst(predicate?: MaybeAsyncPredicate) { + if (predicate) { + for await (const element of this) { + if (await predicate(element)) { + return { + found: true, + element + } as const; + } + } + } else { + const next = await this.iterator().next(); + + if (!next.done) { + return { + found: true, + element: next.value + } as const; + } + } + + return { + found: false + } as const; + } + + async first(predicate?: MaybeAsyncPredicate) { + const result = await this.#tryGetFirst(predicate); + + if (result.found) { + return result.element; + } + + throw new Error("No element was found."); + } + + async firstOrDefault(predicate?: MaybeAsyncPredicate, def?: TElement) { + const result = await this.#tryGetFirst(predicate); + + return result.found ? result.element : def; + } + + async #tryGetLast(predicate?: MaybeAsyncPredicate) { + let found = false; + let result: TElement | undefined = undefined; + + if (predicate) { + for await (const element of this) { + if (await predicate(element)) { + found = true; + result = element; + } + } + } else { + for await (const element of this) { + found = true; + result = element; + } + } + + return { + found, + element: result + }; + } + + async last(predicate?: MaybeAsyncPredicate) { + const result = await this.#tryGetLast(predicate); + + if (result.found) { + return result.element!; + } + + throw new Error("No element was found."); + } + + async lastOrDefault(predicate?: MaybeAsyncPredicate, def?: TElement) { + const result = await this.#tryGetLast(predicate); + + return result.found ? result.element : def; + } + + async #tryGetSingle(predicate?: MaybeAsyncPredicate) { + if (predicate) { + let result: { found: true; element: TElement; } | undefined = undefined; + + for await (const element of this) { + if (await predicate(element)) { + if (result) { + return { + found: false, + reason: 2 + } as const; + } + + result = { + found: true, + element + } as const; + } + } + } else { + const iterator = this.iterator(); + let next = await iterator.next(); + + if (!next.done) { + const result = { + found: true, + element: next.value + } as const; + + next = await iterator.next(); + + if (next.done) { + return result; + } + + return { + found: false, + reason: 2 + } as const; + } + } + + return { + found: false, + reason: 1 + } as const; + } + + async single(predicate?: MaybeAsyncPredicate) { + const result = await this.#tryGetSingle(predicate); + + if (result.found == true) { + return result.element; + } + + let reason: string; + switch (result.reason) { + case 1: + reason = "No element was found."; + break; + case 2: + reason = "More than one element was found."; + break; + default: + reason = ""; + } + throw new Error(reason); + } + + async singleOrDefault(predicate?: MaybeAsyncPredicate, def?: TElement) { + const result = await this.#tryGetSingle(predicate); + + return result.found ? result.element : def; + } + + async #tryElementAt(index: number) { + let i = index; + + for await (const element of this) { + if (i === 0) { + return { + found: true, + element + } as const; + } + + i--; + } + + return { + found: false + } as const; + } + + async elementAt(index: number) { + const result = await this.#tryElementAt(index); + + if (result.found) { + return result.element; + } + + throw new Error("No element found at given index."); + } + + async elementAtOrDefault(index: number, def?: TElement) { + const result = await this.#tryElementAt(index); + + return result.found ? result.element : def; + } + + async aggregate(accumulator: MaybeAsyncAccumulator, seed?: TAccumulator, resultSelector?: MaybeAsyncConverter) { + const iterator = this.iterator(); + + if (seed === undefined) { + const next = await iterator.next(); + + if (next.done) { + throw new Error("Aggregation requires at least one element."); + } + + seed = next.value as unknown as TAccumulator; + } + + let acc = seed; + + while (true) { + const next = await iterator.next(); + + if (next.done) { + break; + } + + acc = await accumulator(acc, next.value); + } + + if (resultSelector) { + return await resultSelector(acc); + } + + return acc as unknown as TResult; + } + + async #find(sorter: MaybeAsyncPredicate, selector?: MaybeAsyncConverter, comparer?: MaybeAsyncComparer) { + const iterator = this.iterator(); + + let next = await iterator.next(); + + if (next.done) { + throw new Error("Sequence contains no element.") + } + + if (!selector) { + selector = identity as MaybeAsyncConverter; + } + + if (!comparer) { + comparer = operatorCompare; + } + + let result = next.value; + let convertedResult = await selector(result); + + while (true) { + next = await iterator.next(); + + if (next.done) { + break; + } + + const value = next.value; + const convertedValue = await selector(value); + + if (await sorter(await comparer(convertedResult, convertedValue))) { + result = value; + convertedResult = convertedValue; + } + } + + return result; + } + + min(comparer?: MaybeAsyncComparer) { + return this.#find(x => x > 0, undefined, comparer); + } + + minBy(converter: MaybeAsyncConverter, comparer?: MaybeAsyncComparer) { + return this.#find(x => x > 0, converter, comparer); + } + + max(comparer?: MaybeAsyncComparer) { + return this.#find(x => x < 0, undefined, comparer); + } + + maxBy(converter: MaybeAsyncConverter, comparer?: MaybeAsyncComparer) { + return this.#find(x => x < 0, converter, comparer); + } + + order(comparer?: MaybeAsyncComparer): OrderedAsyncEnumerable { + return new OrderAsyncEnumerable(this, false, comparer); + } + + orderBy(selector: MaybeAsyncConverter, comparer?: MaybeAsyncComparer): OrderedAsyncEnumerable { + return new OrderByAsyncEnumerable(this, false, selector, comparer); + } + + orderDescending(comparer?: MaybeAsyncComparer): OrderedAsyncEnumerable { + return new OrderAsyncEnumerable(this, true, comparer); + } + + orderByDescending(selector: MaybeAsyncConverter, comparer?: MaybeAsyncComparer): OrderedAsyncEnumerable { + return new OrderByAsyncEnumerable(this, true, selector, comparer); + } + + distinct(equater?: MaybeAsyncEquater): AsyncEnumerable { + return new DistinctAsyncEnumerable(this, equater); + } + + distinctBy(selector: MaybeAsyncConverter, equater?: MaybeAsyncEquater): AsyncEnumerable { + return new DistinctByAsyncEnumerable(this, selector, equater); + } + + union(iterable: MaybeAsyncIterable, equater?: MaybeAsyncEquater): AsyncEnumerable { + return new UnionAsyncEnumerable(this, wrap(iterable), equater); + } + + unionBy(iterable: MaybeAsyncIterable, selector: MaybeAsyncConverter, equater?: MaybeAsyncEquater): AsyncEnumerable { + return new UnionByAsyncEnumerable(this, wrap(iterable), selector, equater); + } + + except(iterable: MaybeAsyncIterable): AsyncEnumerable { + return new ExceptAsyncEnumerable(this, wrap(iterable)); + } + + exceptBy(iterable: MaybeAsyncIterable, selector: MaybeAsyncConverter): AsyncEnumerable { + return new ExceptByAsyncEnumerable(this, wrap(iterable), selector); + } + + intersect(iterable: MaybeAsyncIterable): AsyncEnumerable { + return new IntersectAsyncEnumerable(this, wrap(iterable)); + } + + intersectBy(iterable: MaybeAsyncIterable, selector: MaybeAsyncConverter): AsyncEnumerable { + return new IntersectByAsyncEnumerable(this, wrap(iterable), selector); + } + + async all(predicate: MaybeAsyncPredicate) { + const n = await this.nonEnumeratedCount(); + + if (n === 0) { + return false; + } + + for await (const element of this) { + if (!await predicate(element)) { + return false; + } + } + + return true; + } + + async any(predicate?: MaybeAsyncPredicate) { + const n = await this.nonEnumeratedCount(); + + if (n === 0) { + return false; + } + + if (predicate) { + for await (const element of this) { + if (await predicate(element)) { + return true; + } + } + + return false; + } + + return n < 0 ? !(await this.iterator().next()).done : n > 0; + } + + skip(n: number): AsyncEnumerable { + if (n < 0) { + throw new Error("Cannot skip a negative number of elements."); + } + + return n === 0 ? this : new SkipAsyncEnumerable(this, n); + } + + skipLast(n: number): AsyncEnumerable { + if (n < 0) { + throw new Error("Cannot skip a negative number of elements."); + } + + return n === 0 ? this : new SkipLastAsyncEnumerable(this, n); + } + + skipWhile(predicate: MaybeAsyncPredicate): AsyncEnumerable { + return new SkipWhileAsyncEnumerable(this, predicate); + } + + take(n: number): AsyncEnumerable { + if (n < 0) { + throw new Error("Cannot take a negative number of elements."); + } + + return n === 0 ? empty() : new TakeAsyncEnumerable(this, n); + } + + takeLast(n: number): AsyncEnumerable { + if (n < 0) { + throw new Error("Cannot take a negative number of elements."); + } + + return n === 0 ? empty() : new TakeLastAsyncEnumerable(this, n); + } + + takeWhile(predicate: MaybeAsyncPredicate): AsyncEnumerable { + return new TakeWhileAsyncEnumerable(this, predicate); + } + + peek(action: MaybeAsyncAction): AsyncEnumerable { + return new PeekAsyncEnumerable(this, action); + } + + async forEach(action: MaybeAsyncAction) { + for await (const element of this) { + await action(element); + } + } + + zip(iterable: MaybeAsyncIterable): AsyncEnumerable<[TElement, TOther]> { + return new ZippedAsyncEnumerable(this, wrap(iterable)); + } + + index(): AsyncEnumerable<[number, TElement]> { + return new IndexedAsyncEnumerable(this); + } + + reverse(): AsyncEnumerable { + return new ReversedAsyncEnumerable(this); + } + + chunk(size: number): AsyncEnumerable { + if (size <= 0) { + throw new Error("Chunk size must be positive."); + } + + return new ChunkedAsyncEnumerable(this, size); + } + + cache(): AsyncEnumerable { + return new CacheAsyncEnumerable(this); + } + + async toArray() { + const array = new Array(); + + for await (const element of this) { + array.push(element); + } + + return array; + } + + async toMap(keySelector: MaybeAsyncConverter, valueSelector: MaybeAsyncConverter) { + const map = new Map(); + + for await (const element of this) { + const key = await keySelector(element); + const value = await valueSelector(element); + + map.set(key, value); + } + + return map; + } + + async toSet() { + const set = new Set(); + + for await (const element of this) { + set.add(element); + } + + return set; + } + + async toObject(keySelector: MaybeAsyncConverter, valueSelector: MaybeAsyncConverter) { + const obj: Record = {}; + + for await (const element of this) { + const key = await keySelector(element); + const value = await valueSelector(element); + + obj[key] = value; + } + + return obj; + } +} + +class GroupedAsyncEnumerableImpl extends BaseAsyncEnumerable implements GroupedAsyncEnumerable { + readonly #key: TKey; + readonly #grouping: AsyncEnumerable; + + constructor(key: TKey, grouping: AsyncEnumerable) { + super(); + + this.#key = key; + this.#grouping = grouping; + } + + public get key() { + return this.#key; + } + + override async nonEnumeratedCount() { + return await this.#grouping.nonEnumeratedCount(); + } + + override iterator() { + return this.#grouping.iterator(); + } +} + +abstract class BaseOrderedAsyncEnumerable extends BaseAsyncEnumerable implements OrderedAsyncEnumerable { + readonly #enumerable: AsyncEnumerable; + readonly #sorter: MaybeAsyncComparer; + readonly #descending: boolean; + + constructor(enumerable: AsyncEnumerable, sorter: MaybeAsyncComparer, descending: boolean) { + super(); + + this.#enumerable = enumerable; + this.#sorter = sorter; + this.#descending = descending; + } + + override async nonEnumeratedCount() { + return await this.#enumerable.nonEnumeratedCount(); + } + + get comparer() { + return this.#sorter; + } + + thenSelf(comparer?: MaybeAsyncComparer): OrderedAsyncEnumerable { + return new ThenOrderAsyncEnumerable(this, false, comparer); + } + + thenBy(selector: MaybeAsyncConverter, comparer?: MaybeAsyncComparer): OrderedAsyncEnumerable { + return new ThenOrderByAsyncEnumerable(this, false, selector, comparer); + } + + thenSelfDescending(comparer?: MaybeAsyncComparer): OrderedAsyncEnumerable { + return new ThenOrderAsyncEnumerable(this, true, comparer); + } + + thenByDescending(selector: MaybeAsyncConverter, comparer?: MaybeAsyncComparer): OrderedAsyncEnumerable { + return new ThenOrderByAsyncEnumerable(this, true, selector, comparer); + } + + override async *iterator() { + const arr = new Array(); + + for await (const obj of this.#enumerable) { + arr.push(obj); + } + + //if (this.#sorter) { + await selectionSorter.sort(arr, this.#descending, this.#sorter); + //} else { + // arr.sort(); + + // if (this.#descending) { + // arr.reverse(); + // } + //} + + yield* arr; + } +} + +class EmptyAsyncEnumerable extends BaseAsyncEnumerable { + static readonly INSTANCE = new EmptyAsyncEnumerable(); + + override async nonEnumeratedCount() { + return 0; + } + + override async *iterator() { + + } +} + +class RangeAsyncEnumerable extends BaseAsyncEnumerable { + readonly #min: number; + readonly #max: number; + readonly #step: number; + + constructor(min: number, max: number, step: number) { + super(); + + this.#min = min; + this.#max = max; + this.#step = step; + } + + override async nonEnumeratedCount() { + return Math.ceil((this.#max - this.#min) / this.#step); + } + + override async *iterator() { + for (let i = this.#min; i < this.#max; i += this.#step) { + yield i; + } + } +} + +class RepeatAsyncEnumerable extends BaseAsyncEnumerable { + readonly #value: T; + readonly #count: number; + + constructor(value: T, count: number) { + super(); + + this.#value = value; + this.#count = count; + } + + override async nonEnumeratedCount() { + return this.#count; + } + + override async *iterator() { + let i = this.#count; + + while (i-- > 0) { + yield this.#value; + } + } +} + +class RepeatForeverAsyncEnumerable extends BaseAsyncEnumerable { + readonly #value: T; + + constructor(value: T) { + super(); + + this.#value = value; + } + + override async nonEnumeratedCount() { + return Infinity; + } + + override async *iterator() { + while (true) { + yield this.#value; + } + } +} + +class WrappedObjectAsync extends BaseAsyncEnumerable { + readonly #obj: T; + + constructor(obj: T) { + super(); + + this.#obj = obj; + } + + override async nonEnumeratedCount() { + return 1; + } + + override async *iterator() { + yield this.#obj; + } +} + +class WrappedArrayAsync extends BaseAsyncEnumerable { + readonly #array: T[]; + + constructor(array: T[]) { + super(); + + this.#array = array; + } + + override async nonEnumeratedCount() { + return this.#array.length; + } + + override async *iterator() { + yield* this.#array.values(); + } +} + +class WrappedArrayLikeAsync extends BaseAsyncEnumerable { + readonly #arrayLike: ArrayLike; + + constructor(arrayLike: ArrayLike) { + super(); + + this.#arrayLike = arrayLike; + } + + override async nonEnumeratedCount() { + return this.#arrayLike.length; + } + + override async *iterator() { + for (let i = 0; i < this.#arrayLike.length; i++) { + yield this.#arrayLike[i]; + } + } +} + +class WrappedAsyncIterable extends BaseAsyncEnumerable { + readonly #iterable: AsyncIterable; + + constructor(iterable: AsyncIterable) { + super(); + + this.#iterable = iterable; + } + + override iterator() { + return this.#iterable[Symbol.asyncIterator](); + } +} + +class GeneratorAsyncEnumerable extends BaseAsyncEnumerable { + readonly #generator: () => AsyncGenerator; + + constructor(generator: () => AsyncGenerator) { + super(); + + this.#generator = generator; + } + + override iterator() { + return this.#generator(); + } +} + +class FunctionAsyncEnumerable extends BaseAsyncEnumerable { + readonly #f: () => MaybePromise; + + constructor(f: () => MaybePromise) { + super(); + + this.#f = f; + } + + override async nonEnumeratedCount() { + return Infinity; + } + + override async *iterator() { + while (true) { + yield await this.#f(); + } + } +} + +class WrappedEnumerable extends BaseAsyncEnumerable { + readonly #enumerable: Enumerable; + + constructor(enumerable: Enumerable) { + super(); + + this.#enumerable = enumerable; + } + + override async nonEnumeratedCount() { + return this.#enumerable.nonEnumeratedCount(); + } + + override async *iterator() { + yield* this.#enumerable; + } +} + +class ConcatAsyncEnumerable extends BaseAsyncEnumerable { + readonly #enumerables: Iterable>; + + constructor(enumerables: Iterable>) { + super(); + + this.#enumerables = enumerables; + } + + override async nonEnumeratedCount() { + let n = 0; + + for (const iterable of this.#enumerables) { + const m = await iterable.nonEnumeratedCount(); + + if (m < 0) { + return -1; + } + + n += m; + } + + return n; + } + + override async *iterator() { + for (const enumerable of this.#enumerables) { + yield* enumerable; + } + } +} + +class DistinctAsyncEnumerable extends BaseAsyncEnumerable { + readonly #enumerable: AsyncEnumerable; + readonly #equater: MaybeAsyncEquater | undefined; + + constructor(enumerable: AsyncEnumerable, equater?: MaybeAsyncEquater) { + super(); + + this.#enumerable = enumerable; + this.#equater = equater; + } + + override async *iterator() { + const set = createAsyncEqualitySet(this.#equater); + + for await (const obj of this.#enumerable) { + if (await set.add(obj)) { + yield obj; + } + } + } +} + +class DistinctByAsyncEnumerable extends BaseAsyncEnumerable { + readonly #enumerable: AsyncEnumerable; + readonly #selector: MaybeAsyncConverter; + readonly #equater: MaybeAsyncEquater | undefined; + + constructor(enumerable: AsyncEnumerable, selector: MaybeAsyncConverter, equater?: MaybeAsyncEquater) { + super(); + + this.#enumerable = enumerable; + this.#selector = selector; + this.#equater = equater; + } + + override async *iterator() { + const set = createAsyncEqualitySet(this.#equater); + + for await (const obj of this.#enumerable) { + if (await set.add(await this.#selector(obj))) { + yield obj; + } + } + + await set.clear(); + } +} + +class FilterAsyncEnumerable extends BaseAsyncEnumerable { + readonly #enumerable: AsyncEnumerable; + readonly #predicate: MaybeAsyncPredicate; + + constructor(enumerable: AsyncEnumerable, predicate: MaybeAsyncPredicate) { + super(); + + this.#enumerable = enumerable; + this.#predicate = predicate; + } + + override async *iterator() { + for await (const obj of this.#enumerable) { + if (await this.#predicate(obj)) { + yield obj; + } + } + } +} + +class FlatMapperAsyncEnumerable extends BaseAsyncEnumerable { + readonly #enumerable: AsyncEnumerable; + readonly #converter: MaybeAsyncConverter>; + + constructor(enumerable: AsyncEnumerable, converter: MaybeAsyncConverter>) { + super(); + + this.#enumerable = enumerable; + this.#converter = converter; + } + + override async *iterator() { + for await (const obj of this.#enumerable) { + yield* await this.#converter(obj); + } + } +} + +class IndexedAsyncEnumerable extends BaseAsyncEnumerable<[number, T]> { + readonly #enumerable: AsyncEnumerable; + + constructor(enumerable: AsyncEnumerable) { + super(); + + this.#enumerable = enumerable; + } + + override async *iterator() { + let i = 0; + + for await (const obj of this.#enumerable) { + yield [i++, obj] as [number, T]; + } + } +} + +class MapperAsyncEnumerable extends BaseAsyncEnumerable { + readonly #enumerable: AsyncEnumerable; + readonly #converter: MaybeAsyncConverter; + + constructor(enumerable: AsyncEnumerable, converter: MaybeAsyncConverter) { + super(); + + this.#enumerable = enumerable; + this.#converter = converter; + } + + override async nonEnumeratedCount() { + return await this.#enumerable.nonEnumeratedCount(); + } + + override async *iterator() { + for await (const obj of this.#enumerable) { + yield await this.#converter(obj); + } + } +} + +class SkipWhileAsyncEnumerable extends BaseAsyncEnumerable { + readonly #enumerable: AsyncEnumerable; + readonly #predicate: MaybeAsyncPredicate; + + constructor(enumerable: AsyncEnumerable, predicate: MaybeAsyncPredicate) { + super(); + + this.#enumerable = enumerable; + this.#predicate = predicate; + } + + override async *iterator() { + const iterator = this.#enumerable.iterator(); + + while (true) { + const next = await iterator.next(); + + if (next.done) { + return; + } + + if (await this.#predicate(next.value)) { + continue; + } + + yield next.value; + break; + } + + yield* asAsyncGenerator(iterator); + } +} + +class SkipLastAsyncEnumerable extends BaseAsyncEnumerable { + readonly #enumerable: AsyncEnumerable; + readonly #n: number; + + constructor(enumerable: AsyncEnumerable, n: number) { + super(); + + this.#enumerable = enumerable; + this.#n = n; + } + + override async nonEnumeratedCount() { + const n = await this.#enumerable.nonEnumeratedCount(); + return n < 0 ? -1 : Math.max(0, n - this.#n); + } + + override async *iterator() { + const iterator = this.#enumerable.iterator(); + const buffer = new Array(this.#n); // n > 0 + let i = 0; + + do { + const next = await iterator.next(); + + if (next.done) { + return; + } + + buffer[i++] = next.value; + } while (i < this.#n); + + i = 0; + + for await (const obj of asAsyncGenerator(iterator)) { + yield buffer[i]; + buffer[i] = obj; + i = (i + 1) % this.#n; + } + } +} + +class SkipAsyncEnumerable extends BaseAsyncEnumerable { + readonly #enumerable: AsyncEnumerable; + readonly #n: number; + + constructor(enumerable: AsyncEnumerable, n: number) { + super(); + + this.#enumerable = enumerable; + this.#n = n; + } + + override async nonEnumeratedCount() { + const n = await this.#enumerable.nonEnumeratedCount(); + return n < 0 ? -1 : Math.max(0, n - this.#n); + } + + override async *iterator() { + const iterator = this.#enumerable.iterator(); + let i = 0; + + do { + if ((await iterator.next()).done) { + return; + } + + i++; + } while (i < this.#n); + + yield* asAsyncGenerator(iterator); + } +} + +class TakeWhileAsyncEnumerable extends BaseAsyncEnumerable { + readonly #enumerable: AsyncEnumerable; + readonly #predicate: MaybeAsyncPredicate; + + constructor(enumerable: AsyncEnumerable, predicate: MaybeAsyncPredicate) { + super(); + + this.#enumerable = enumerable; + this.#predicate = predicate; + } + + override async *iterator() { + for await (const obj of this.#enumerable) { + if (!await this.#predicate(obj)) { + return; + } + + yield obj; + } + } +} + +class TakeLastAsyncEnumerable extends BaseAsyncEnumerable { + readonly #enumerable: AsyncEnumerable; + readonly #n: number; + + constructor(enumerable: AsyncEnumerable, n: number) { + super(); + + this.#enumerable = enumerable; + this.#n = n; + } + + override async nonEnumeratedCount() { + const n = await this.#enumerable.nonEnumeratedCount(); + return n < 0 ? -1 : Math.min(this.#n, n); + } + + override async *iterator() { + const queue = createQueue(this.#n); + + for await (const obj of this.#enumerable) { + queue.enqueue(obj); + } + + yield* queue; + } +} + +class TakeAsyncEnumerable extends BaseAsyncEnumerable { + readonly #enumerable: AsyncEnumerable; + readonly #n: number; + + constructor(enumerable: AsyncEnumerable, n: number) { + super(); + + this.#enumerable = enumerable; + this.#n = n; + } + + override async nonEnumeratedCount() { + const n = await this.#enumerable.nonEnumeratedCount(); + return n < 0 ? -1 : Math.min(this.#n, n); + } + + override async *iterator() { + const iterator = this.#enumerable.iterator(); + let i = this.#n; + + while (i > 0) { + const next = await iterator.next(); + + if (next.done) { + return; + } + + yield next.value as T; + i--; + } + } +} + +class OrderAsyncEnumerable extends BaseOrderedAsyncEnumerable { + constructor(enumerable: AsyncEnumerable, descending: boolean, sorter?: MaybeAsyncComparer) { + super(enumerable, sorter ?? defaultArrayComparer, descending); + } +} + +class OrderByAsyncEnumerable extends BaseOrderedAsyncEnumerable { + constructor(enumerable: AsyncEnumerable, descending: boolean, selector: MaybeAsyncConverter, sorter?: MaybeAsyncComparer) { + super(enumerable, OrderByAsyncEnumerable.#createSorter(selector, sorter), descending); + } + + static #createSorter(selector: MaybeAsyncConverter, sorter?: MaybeAsyncComparer) { + sorter ??= defaultArrayComparer; + return async (a: T, b: T) => sorter(await selector(a), await selector(b)); + } +} + +class ThenOrderAsyncEnumerable extends BaseOrderedAsyncEnumerable { + constructor(enumerable: OrderedAsyncEnumerable, descending: boolean, sorter?: MaybeAsyncComparer) { + super(enumerable, combineAsyncComparers(enumerable.comparer ?? defaultArrayComparer, sorter ?? defaultArrayComparer), descending); + } +} + +class ThenOrderByAsyncEnumerable extends BaseOrderedAsyncEnumerable { + constructor(enumerable: OrderedAsyncEnumerable, descending: boolean, selector: MaybeAsyncConverter, sorter?: MaybeAsyncComparer) { + super(enumerable, ThenOrderByAsyncEnumerable.#createCombinedSorter(enumerable.comparer, selector, sorter), descending); + } + + static #createCombinedSorter(baseSorter: MaybeAsyncComparer, selector: MaybeAsyncConverter, sorter?: MaybeAsyncComparer) { + baseSorter ??= defaultArrayComparer; + sorter ??= defaultArrayComparer; + return combineAsyncComparers(baseSorter, async (a: T, b: T) => sorter(await selector(a), await selector(b))); + } +} + +class AppendAsyncEnumerable extends BaseAsyncEnumerable { + readonly #enumerable: AsyncEnumerable; + readonly #obj: T; + + constructor(enumerable: AsyncEnumerable, obj: T) { + super(); + + this.#enumerable = enumerable; + this.#obj = obj; + } + + override async nonEnumeratedCount() { + const n = await this.#enumerable.nonEnumeratedCount(); + return n < 0 ? -1 : n + 1; + } + + override async *iterator() { + yield* this.#enumerable; + yield this.#obj; + } +} + +class PrependAsyncEnumerable extends BaseAsyncEnumerable { + readonly #enumerable: AsyncEnumerable; + readonly #obj: T; + + constructor(enumerable: AsyncEnumerable, obj: T) { + super(); + + this.#enumerable = enumerable; + this.#obj = obj; + } + + override async nonEnumeratedCount() { + const n = await this.#enumerable.nonEnumeratedCount(); + return n < 0 ? -1 : n + 1; + } + + override async *iterator() { + yield this.#obj; + yield* this.#enumerable; + } +} + +class PeekAsyncEnumerable extends BaseAsyncEnumerable { + readonly #enumerable: AsyncEnumerable; + readonly #action: MaybeAsyncAction; + + constructor(enumerable: AsyncEnumerable, action: MaybeAsyncAction) { + super(); + + this.#enumerable = enumerable; + this.#action = action; + } + + override async nonEnumeratedCount() { + return await this.#enumerable.nonEnumeratedCount(); + } + + override async *iterator() { + for await (const obj of this.#enumerable) { + await this.#action(obj); + yield obj; + } + } +} + +class ZippedAsyncEnumerable extends BaseAsyncEnumerable<[T, U]> { + readonly #first: AsyncEnumerable; + readonly #second: AsyncEnumerable; + + constructor(first: AsyncEnumerable, second: AsyncEnumerable) { + super(); + + this.#first = first; + this.#second = second; + } + + override async nonEnumeratedCount() { + const first = await this.#first.nonEnumeratedCount(); + const second = await this.#second.nonEnumeratedCount(); + + return first < 0 || second < 0 ? -1 : Math.min(first, second); + } + + override async *iterator() { + const firstIterator = this.#first.iterator(); + const secondIterator = this.#second.iterator(); + + while (true) { + const firstNext = await firstIterator.next(); + const secondNext = await secondIterator.next(); + + if (firstNext.done || secondNext.done) { + return; + } + + yield [firstNext.value, secondNext.value] as [T, U]; + } + } +} + +class UnionAsyncEnumerable extends BaseAsyncEnumerable { + readonly #first: AsyncEnumerable; + readonly #second: AsyncEnumerable; + readonly #equater: MaybeAsyncEquater | undefined; + + constructor(first: AsyncEnumerable, second: AsyncEnumerable, equater?: MaybeAsyncEquater) { + super(); + + this.#first = first; + this.#second = second; + this.#equater = equater; + } + + override async *iterator() { + const set = createAsyncEqualitySet(this.#equater); + + for await (const obj of this.#first) { + if (await set.add(obj)) { + yield obj; + } + } + + for await (const obj of this.#second) { + if (await set.add(obj)) { + yield obj; + } + } + + await set.clear(); + } +} + +class UnionByAsyncEnumerable extends BaseAsyncEnumerable { + readonly #first: AsyncEnumerable; + readonly #second: AsyncEnumerable; + readonly #selector: MaybeAsyncConverter; + readonly #equater: MaybeAsyncEquater | undefined; + + constructor(first: AsyncEnumerable, second: AsyncEnumerable, selector: MaybeAsyncConverter, equater?: MaybeAsyncEquater) { + super(); + + this.#first = first; + this.#second = second; + this.#selector = selector; + this.#equater = equater; + } + + override async *iterator() { + const set = createAsyncEqualitySet(this.#equater); + + for await (const obj of this.#first) { + if (await set.add(await this.#selector(obj))) { + yield obj; + } + } + + for await (const obj of this.#second) { + if (await set.add(await this.#selector(obj))) { + yield obj; + } + } + + await set.clear(); + } +} + +class ExceptAsyncEnumerable extends BaseAsyncEnumerable { + readonly #first: AsyncEnumerable; + readonly #second: AsyncEnumerable; + readonly #equater: MaybeAsyncEquater | undefined; + + constructor(first: AsyncEnumerable, second: AsyncEnumerable, equater?: MaybeAsyncEquater) { + super(); + + this.#first = first; + this.#second = second; + this.#equater = equater; + } + + override async *iterator() { + const set = createAsyncEqualitySet(this.#equater); + + for await (const obj of this.#second) { + await set.add(obj); + } + + for await (const obj of this.#first) { + if (await set.add(obj)) { + yield obj; + } + } + + await set.clear(); + } +} + +class ExceptByAsyncEnumerable extends BaseAsyncEnumerable { + readonly #first: AsyncEnumerable; + readonly #second: AsyncEnumerable; + readonly #selector: MaybeAsyncConverter; + readonly #equater: MaybeAsyncEquater | undefined; + + constructor(first: AsyncEnumerable, second: AsyncEnumerable, selector: MaybeAsyncConverter, equater?: MaybeAsyncEquater) { + super(); + + this.#first = first; + this.#second = second; + this.#selector = selector; + this.#equater = equater; + } + + override async *iterator() { + const set = createAsyncEqualitySet(this.#equater); + + for await (const obj of this.#second) { + await set.add(await this.#selector(obj)); + } + + for await (const obj of this.#first) { + if (await set.add(await this.#selector(obj))) { + yield obj; + } + } + + await set.clear(); + } +} + +class IntersectAsyncEnumerable extends BaseAsyncEnumerable { + readonly #first: AsyncEnumerable; + readonly #second: AsyncEnumerable; + readonly #equater: MaybeAsyncEquater | undefined; + + constructor(first: AsyncEnumerable, second: AsyncEnumerable, equater?: MaybeAsyncEquater) { + super(); + + this.#first = first; + this.#second = second; + this.#equater = equater; + } + + override async *iterator() { + const set = createAsyncEqualitySet(this.#equater); + + for await (const obj of this.#second) { + await set.add(obj); + } + + for await (const obj of this.#first) { + if (await set.remove(obj)) { + yield obj; + } + } + + await set.clear(); + } +} + +class IntersectByAsyncEnumerable extends BaseAsyncEnumerable { + readonly #first: AsyncEnumerable; + readonly #second: AsyncEnumerable; + readonly #selector: MaybeAsyncConverter; + readonly #equater: MaybeAsyncEquater | undefined; + + constructor(first: AsyncEnumerable, second: AsyncEnumerable, selector: MaybeAsyncConverter, equater?: MaybeAsyncEquater) { + super(); + + this.#first = first; + this.#second = second; + this.#selector = selector; + this.#equater = equater; + } + + override async *iterator() { + const set = createAsyncEqualitySet(this.#equater); + + for await (const obj of this.#second) { + await set.add(await this.#selector(obj)); + } + + for await (const obj of this.#first) { + if (await set.remove(await this.#selector(obj))) { + yield obj; + } + } + + await set.clear(); + } +} + +class ReversedAsyncEnumerable extends BaseAsyncEnumerable { + readonly #enumerable: AsyncEnumerable; + + constructor(enumerable: AsyncEnumerable) { + super(); + + this.#enumerable = enumerable; + } + + override async *iterator() { + const buffer = new Array(); + + for await (const obj of this.#enumerable) { + buffer.push(obj); + } + + for (let i = buffer.length - 1; i >= 0; i--) { + yield buffer[i]; + } + } +} + +class GroupByAsyncEnumerable extends BaseAsyncEnumerable> { + readonly #enumerable: AsyncEnumerable; + readonly #keySelector: MaybeAsyncConverter; + readonly #elementSelector: MaybeAsyncConverter; + readonly #keyComparer: MaybeAsyncEquater | undefined; + + constructor(enumerable: AsyncEnumerable, keySelector: MaybeAsyncConverter, elementSelector?: MaybeAsyncConverter, keyComparer?: MaybeAsyncEquater) { + super(); + + this.#enumerable = enumerable; + this.#keySelector = keySelector; + this.#elementSelector = elementSelector ?? identity as MaybeAsyncConverter; + this.#keyComparer = keyComparer; + } + + override async *iterator() { + const groupings = createAsyncEqualityMap(this.#keyComparer); + + for await (const obj of this.#enumerable) { + const key = await this.#keySelector(obj); + let grouping = await groupings.get(key); + + if (!grouping) { + await groupings.set(key, grouping = []); + } + + grouping.push(await this.#elementSelector(obj)); + } + + for await (const entry of groupings) { + yield new GroupedAsyncEnumerableImpl(entry[0], array(entry[1])); + } + } +} + +class ChunkedAsyncEnumerable extends BaseAsyncEnumerable { + readonly #enumerable: AsyncEnumerable; + readonly #size: number; + + constructor(enumerable: AsyncEnumerable, size: number) { + super(); + + this.#enumerable = enumerable; + this.#size = size; + } + + override async nonEnumeratedCount() { + const n = await this.#enumerable.nonEnumeratedCount(); + return n < 0 ? -1 : Math.ceil(n / this.#size); + } + + override async *iterator() { + let chunk = new Array(); + + for await (const obj of this.#enumerable) { + chunk.push(obj); + + if (chunk.length === this.#size) { + yield chunk; + chunk = new Array(); + } + } + + if (chunk.length > 0) { + yield chunk; + } + } +} + +class JoinAsyncEnumerable extends BaseAsyncEnumerable { + readonly #first: AsyncEnumerable; + readonly #second: AsyncEnumerable; + readonly #firstKeySelector: MaybeAsyncConverter; + readonly #secondKeySelector: MaybeAsyncConverter; + readonly #resultSelector: MaybeAsyncBiConverter; + readonly #keyComparer: MaybeAsyncEquater; + + constructor(first: AsyncEnumerable, second: AsyncEnumerable, firstKeySelector: MaybeAsyncConverter, secondKeySelector: MaybeAsyncConverter, resultSelector?: MaybeAsyncBiConverter, keyComparer?: MaybeAsyncEquater) { + super(); + + this.#first = first; + this.#second = second; + this.#firstKeySelector = firstKeySelector; + this.#secondKeySelector = secondKeySelector; + this.#resultSelector = resultSelector ?? identity as MaybeAsyncBiConverter; + this.#keyComparer = keyComparer ?? strictEquals; + } + + override async *iterator() { + for await (const firstObj of this.#first) { + const firstKey = await this.#firstKeySelector(firstObj); + + for await (const secondObj of this.#second) { + const secondKey = await this.#secondKeySelector(secondObj); + + if (await this.#keyComparer(firstKey, secondKey)) { + yield await this.#resultSelector(firstObj, secondObj); + } + } + } + } +} + +class GroupJoinAsyncEnumerable extends BaseAsyncEnumerable { + readonly #first: AsyncEnumerable; + readonly #second: AsyncEnumerable; + readonly #firstKeySelector: MaybeAsyncConverter; + readonly #secondKeySelector: MaybeAsyncConverter; + readonly #resultSelector: MaybeAsyncBiConverter, TResult>; + readonly #keyComparer: MaybeAsyncEquater; + + constructor(first: AsyncEnumerable, second: AsyncEnumerable, firstKeySelector: MaybeAsyncConverter, secondKeySelector: MaybeAsyncConverter, resultSelector?: MaybeAsyncBiConverter, TResult>, keyComparer?: MaybeAsyncEquater) { + super(); + + this.#first = first; + this.#second = second; + this.#firstKeySelector = firstKeySelector; + this.#secondKeySelector = secondKeySelector; + this.#resultSelector = resultSelector ?? GroupJoinAsyncEnumerable.#defaultResultSelector as MaybeAsyncBiConverter, TResult>; + this.#keyComparer = keyComparer ?? strictEquals; + } + + static #defaultResultSelector(first: TOuter, second: AsyncEnumerable) { + return new GroupedAsyncEnumerableImpl(first, second); + } + + override async *iterator() { + for await (const firstObj of this.#first) { + const firstKey = await this.#firstKeySelector(firstObj); + const secondObjs = new Array(); + + for await (const secondObj of this.#second) { + const secondKey = await this.#secondKeySelector(secondObj); + + if (await this.#keyComparer(firstKey, secondKey)) { + secondObjs.push(secondObj); + } + } + + // yield this.#resultSelector(firstObj, this.#second.where(secondObj => this.#keyComparer(firstKey, this.#secondKeySelector(secondObj)))); + yield this.#resultSelector(firstObj, array(secondObjs)); + } + } +} + +class RemoveAsyncEnumerable extends BaseAsyncEnumerable { + readonly #enumerable: AsyncEnumerable; + readonly #obj: T; + readonly #all: boolean; + readonly #equater: MaybeAsyncEquater; + + constructor(enumerable: AsyncEnumerable, obj: T, all?: boolean, equater?: MaybeAsyncEquater) { + super(); + + this.#enumerable = enumerable; + this.#obj = obj; + this.#all = all ?? false; + this.#equater = equater ?? strictEquals; + } + + override async *iterator() { + let gotOne = false; + + for await (const obj of this.#enumerable) { + if (await this.#equater(this.#obj, obj)) { + if (this.#all) { + continue; + } + + if (!gotOne) { + gotOne = true; + continue; + } + } + + yield obj; + } + } +} + +class CacheAsyncEnumerable extends BaseAsyncEnumerable { + readonly #enumerable: AsyncEnumerable; + #cache: T[] | undefined; + + constructor(enumerable: AsyncEnumerable) { + super(); + + this.#enumerable = enumerable; + } + + override async *iterator() { + yield* this.#cache ??= await this.#enumerable.toArray(); + } +} + +class AwaitedAsyncEnumerable extends BaseAsyncEnumerable { + readonly #enumerable: AsyncEnumerable; + #cache: T[] | undefined; + + constructor(enumerable: AsyncEnumerable) { + super(); + + this.#enumerable = enumerable; + } + + override async *iterator() { + yield* this.#cache ??= await this.#enumerable.toArray(); + } +} + +//#endregion diff --git a/src/bitarray.ts b/src/bitarray.ts new file mode 100644 index 0000000..cc0b0d1 --- /dev/null +++ b/src/bitarray.ts @@ -0,0 +1,254 @@ +import { join } from "./collector.js"; +import { arrayLike, sequence } from "./sync.js"; +import { asArray, isDefined } from "./utils.js"; + +const BYTE_SIZE = Uint8Array.BYTES_PER_ELEMENT * 8; +const FULL_BYTE = getMask(BYTE_SIZE); + +function getMask(bitCount: number) { + return ~(~0b0 << bitCount); +} + +function getByteIndex(index: number) { + return Math.floor(index / BYTE_SIZE); +} + +function getBitIndex(index: number) { + return index % BYTE_SIZE; +} + +function setBit(byte: number, bitIndex: number, value: boolean) { + const mask = 0b1 << bitIndex; + return value ? (byte | mask) : (byte & ~mask); +} + +function* yieldBits(byte: number, bitCount: number) { + for (let i = 0; i < bitCount; i++) { + yield Boolean((byte >>> i) & 0b1); + } +} + +function* getBytes(bits: Iterable) { + const bitIterator = bits[Symbol.iterator](); + + while (true) { + let next = bitIterator.next(); + + if (next.done) { + break; + } + + let byte = 0; + + for (let i = 0; i < BYTE_SIZE; i++) { + byte |= Number(next.value) << i; + + next = bitIterator.next(); + + if (next.done) { + break; + } + } + + yield byte; + } +} + +export class BitArray implements Iterable { + readonly #length: number; + readonly #bits: Uint8Array; + readonly #wholeBytes: number; + readonly #remainingBits: number; + readonly #remainingBitsMask: number; + + public constructor(length: number) { + const exactBytes = length / BYTE_SIZE; + const remainingBits = length % BYTE_SIZE; + + this.#length = length; + this.#bits = new Uint8Array(Math.ceil(exactBytes)); + this.#wholeBytes = Math.floor(exactBytes); + this.#remainingBits = remainingBits; + this.#remainingBitsMask = getMask(remainingBits); + } + + public static from(bits: Iterable) { + const arr = asArray(bits); + const result = new this(arr.length); + + let i = 0; + for (const bit of arr) { + result.set(i++, bit); + } + + return result; + } + + public static of(...bits: boolean[]) { + return this.from(bits); + } + + *[Symbol.iterator]() { + for (let i = 0; i < this.#wholeBytes; i++) { + yield* yieldBits(this.#bits[i], BYTE_SIZE); + } + + if (this.#remainingBits > 0) { + yield* yieldBits(this.#bits[this.#wholeBytes], this.#remainingBits); + } + } + + #ensureValidIndex(index: number) { + if (index < 0 || index >= this.#length) { + throw new RangeError("The index is outside the BitArray range."); + } + } + + #ensureSameSize(other: BitArray) { + if (this.#length !== other.length) { + throw new TypeError("The BitArrays do not have the same length."); + } + } + + public get length() { + return this.#length; + } + + public get(index: number) { + this.#ensureValidIndex(index); + + return Boolean((this.#bits[getByteIndex(index)] >>> getBitIndex(index)) & 0b1); + } + + public set(index: number, value: boolean) { + this.#ensureValidIndex(index); + + const byteIndex = getByteIndex(index); + this.#bits[byteIndex] = setBit(this.#bits[byteIndex], getBitIndex(index), value); + } + + public isFull() { + // return enumerable.sequence(this).all(bit => bit); + return this.#bits.subarray(0, this.#wholeBytes).every(byte => byte === FULL_BYTE) && (this.#remainingBits === 0 || this.#bits[this.#wholeBytes] === this.#remainingBitsMask); + } + + public isEmpty() { + // return enumerable.sequence(this).none(bit => bit); + return this.#bits.every(byte => byte === 0); + } + + public fill(value: boolean) { + const b = value ? FULL_BYTE : 0; + for (let i = 0; i < this.#wholeBytes; i++) { + this.#bits[i] = b; + } + + if (this.#remainingBits > 0) { + this.#bits[this.#wholeBytes] = value ? this.#remainingBitsMask : 0; + } + } + + public and(other: BitArray) { + this.#ensureSameSize(other); + + // let i = 0; + // for (const byte of getBytes(other)) { + // this.#bits[i++] &= byte; + // } + + for (let i = 0; i < this.#length; i++) { + this.#bits[i] &= other.#bits[i]; + } + + return this; + } + + public or(other: BitArray) { + this.#ensureSameSize(other); + + // let i = 0; + // for (const byte of getBytes(other)) { + // this.#bits[i++] |= byte; + // } + + for (let i = 0; i < this.#length; i++) { + this.#bits[i] |= other.#bits[i]; + } + + return this; + } + + public xor(other: BitArray) { + this.#ensureSameSize(other); + + // let i = 0; + // for (const byte of getBytes(other)) { + // this.#bits[i++] ^= byte; + // } + + for (let i = 0; i < this.#length; i++) { + this.#bits[i] ^= other.#bits[i]; + } + + return this; + } + + public not() { + for (let i = 0; i < this.#wholeBytes; i++) { + this.#bits[i] = ~this.#bits[i] & FULL_BYTE; + } + + if (this.#remainingBits > 0) { + this.#bits[this.#wholeBytes] = ~this.#bits[this.#wholeBytes] & this.#remainingBitsMask; + } + + return this; + } + + public andNot(other: BitArray) { + this.#ensureSameSize(other); + + // let i = 0; + // for (const byte of getBytes(other)) { + // this.#bits[i++] &= ~byte; + // } + + for (let i = 0; i < this.#length; i++) { + this.#bits[i] &= ~other.#bits[i]; + } + + return this; + } + + public contains(other: BitArray) { + this.#ensureSameSize(other); + + // return enumerable.sequence(this).zip(enumerable.sequence(other)).where(([, b]) => b).all(([a, b]) => a && b); + return arrayLike(this.#bits).zip(arrayLike(other.#bits)).all(([a, b]) => (a & b) === b); + } + + public intersects(other: BitArray) { + this.#ensureSameSize(other); + + // return enumerable.sequence(this).zip(enumerable.sequence(other)).any(([a, b]) => a && b); + return arrayLike(this.#bits).zip(arrayLike(other.#bits)).any(([a, b]) => (a & b) !== 0); + } + + public copy() { + const copy = new BitArray(this.#length); + copy.#bits.set(this.#bits); + return copy; + } + + public toArray() { + return sequence(this).toArray(); + } + + public equals(other: BitArray) { + return other === this || isDefined(other) && arrayLike(this.#bits).equals(arrayLike(other.#bits)); + } + + public toString() { + return sequence(this).select(bit => bit ? '1' : '0').collect(join()); + } +} diff --git a/src/collector.ts b/src/collector.ts new file mode 100644 index 0000000..460235d --- /dev/null +++ b/src/collector.ts @@ -0,0 +1,250 @@ +import { Converter } from "./sync.js"; + +export interface Collector { + initialize(): TAccumulator; + accumulate(accumulator: TAccumulator, element: TElement): void; + finalize(accumulator: TAccumulator): TResult; +} + +// export interface Collector2 { +// accumulate(element: TElement): void; +// finalize(): TResult; +// } + +class SimpleCollector implements Collector { + readonly #initialize: () => TAccumulator; + readonly #accumulate: (accumulator: TAccumulator, element: TElement) => void; + readonly #finalize: (accumulator: TAccumulator) => TResult; + + constructor(initialize: () => TAccumulator, accumulate: (accumulator: TAccumulator, element: TElement) => void, + finalize: (accumulator: TAccumulator) => TResult) { + this.#initialize = initialize; + this.#accumulate = accumulate; + this.#finalize = finalize; + } + + initialize() { + return this.#initialize(); + } + + accumulate(accumulator: TAccumulator, element: TElement) { + this.#accumulate(accumulator, element); + } + + finalize(accumulator: TAccumulator): TResult { + return this.#finalize(accumulator); + } +} + +export function create(initialize: () => TAccumulator, accumulate: (accumulator: TAccumulator, element: TElement) => void, + finalize: (accumulator: TAccumulator) => TResult): Collector { + return new SimpleCollector(initialize, accumulate, finalize); +} + +class ToArrayCollector implements Collector { + initialize(): TElement[] { + return []; + } + + accumulate(accumulator: TElement[], element: TElement) { + accumulator.push(element); + } + + finalize(accumulator: TElement[]) { + return accumulator; + } +} + +const toArrayCollector = new ToArrayCollector(); + +export function toArray(): Collector { + return toArrayCollector; +} + +class ToObjectCollector implements Collector, Record> { + readonly #keySelector: Converter; + readonly #valueSelector: Converter; + + constructor(keySelector: Converter, valueSelector: Converter) { + this.#keySelector = keySelector; + this.#valueSelector = valueSelector; + } + + initialize() { + return {} as Record; + } + + accumulate(accumulator: Record, element: TElement) { + const key = this.#keySelector(element); + const value = this.#valueSelector(element); + + accumulator[key] = value; + } + + finalize(accumulator: Record) { + return accumulator; + } +} + +export function toObject(keySelector: Converter, valueSelector: Converter): Collector> { + return new ToObjectCollector(keySelector, valueSelector); +} + +class ToMapCollector implements Collector, Map> { + readonly #keySelector: Converter; + readonly #valueSelector: Converter; + + constructor(keySelector: Converter, valueSelector: Converter) { + this.#keySelector = keySelector; + this.#valueSelector = valueSelector; + } + + initialize() { + return new Map(); + } + + accumulate(accumulator: Map, element: TElement) { + const key = this.#keySelector(element); + const value = this.#valueSelector(element); + + accumulator.set(key, value); + } + + finalize(accumulator: Map) { + return accumulator; + } +} + +export function toMap(keySelector: Converter, valueSelector: Converter): Collector> { + return new ToMapCollector(keySelector, valueSelector); +} + +class ToSetCollector implements Collector, Set> { + initialize() { + return new Set(); + } + + accumulate(accumulator: Set, element: TElement) { + accumulator.add(element); + } + + finalize(accumulator: Set) { + return accumulator; + } +} + +const toSetCollector = new ToSetCollector(); + +export function toSet(): Collector> { + return toSetCollector; +} + +class JoinCollector implements Collector { + readonly #delimiter: string; + readonly #prefix: string; + readonly #suffix: string; + + constructor(delimiter?: string, prefix?: string, suffix?: string) { + this.#delimiter = delimiter ?? ""; + this.#prefix = prefix ?? ""; + this.#suffix = suffix ?? ""; + } + + initialize(): any[] { + return []; + } + + accumulate(accumulator: any[], element: string) { + accumulator.push(element); + } + + finalize(accumulator: any[]) { + return this.#prefix + accumulator.join(this.#delimiter) + this.#suffix; + } +} + +export function join(delimiter?: string, prefix?: string, suffix?: string): Collector { + return new JoinCollector(delimiter, prefix, suffix); +} + +class SumCollector implements Collector { + initialize() { + return { sum: 0 }; + } + + accumulate(accumulator: { sum: number }, element: number) { + accumulator.sum += element; + } + + finalize(accumulator: { sum: number }) { + return accumulator.sum; + } +} + +const sumCollector = new SumCollector(); + +export function sum(): Collector { + return sumCollector; +} + +class BigIntSumCollector implements Collector { + initialize() { + return { sum: 0n, }; + } + + accumulate(accumulator: { sum: bigint }, element: bigint) { + accumulator.sum += element; + } + + finalize(accumulator: { sum: bigint }) { + return accumulator.sum; + } +} + +const bigintSumCollector = new BigIntSumCollector(); + +export function bigintSum(): Collector { + return bigintSumCollector; +} + +class AverageCollector implements Collector { + initialize() { + return { count: 0, sum: 0 }; + } + + accumulate(accumulator: { count: number, sum: number }, element: number) { + accumulator.count++; + accumulator.sum += element; + } + + finalize(accumulator: { count: number, sum: number }) { + return accumulator.count === 0 ? 0 : accumulator.sum / accumulator.count; + } +} + +const averageCollector = new AverageCollector(); + +export function average(): Collector { + return averageCollector; +} + +class BigIntAverageCollector implements Collector { + initialize() { + return { count: 0, sum: 0n }; + } + + accumulate(accumulator: { count: number, sum: bigint }, element: bigint) { + accumulator.count++; + accumulator.sum += element; + } + + finalize(accumulator: { count: number, sum: bigint }) { + return accumulator.count === 0 ? 0n : accumulator.sum / BigInt(accumulator.count); + } +} + +const bigintAverageCollector = new BigIntAverageCollector(); + +export function bigintAverage(): Collector { + return bigintAverageCollector; +} diff --git a/src/equality-map.ts b/src/equality-map.ts new file mode 100644 index 0000000..86170ec --- /dev/null +++ b/src/equality-map.ts @@ -0,0 +1,219 @@ +import { Equater } from "./sync.js"; +import { MaybeAsyncEquater } from "./async.js"; +import { asAsyncGenerator } from "./utils.js"; + +export interface EqualityMap extends Iterable<[K, V]> { + get(key: K): V | undefined; + set(key: K, value: V): V | undefined; + contains(key: K): boolean; + remove(key: K): V | undefined; + clear(): void; +} + +class NativeEqualityMap implements EqualityMap { + readonly #map = new Map(); + + get(key: K) { + return this.#map.get(key); + } + + set(key: K, value: V) { + const existing = this.get(key); + this.#map.set(key, value); + return existing; + } + + contains(key: K) { + return this.#map.has(key); + } + + remove(key: K) { + const existing = this.get(key); + this.#map.delete(key); + return existing; + } + + clear() { + this.#map.clear(); + } + + [Symbol.iterator]() { + return this.#map[Symbol.iterator](); + } +} + +class CustomEqualityMap implements EqualityMap { + readonly #keyComparer: Equater; + readonly #list = new Array<[K, V]>(); + + constructor(keyComparer: Equater) { + this.#keyComparer = keyComparer; + } + + get(key: K) { + for (const entry of this.#list) { + if (this.#keyComparer(key, entry[0])) { + return entry[1]; + } + } + + return undefined; + } + + set(key: K, value: V) { + for (const entry of this.#list) { + if (this.#keyComparer(key, entry[0])) { + const previous = entry[1]; + entry[1] = value; + return previous; + } + } + + this.#list.push([key, value]); + + return undefined; + } + + contains(key: K) { + for (const entry of this.#list) { + if (this.#keyComparer(key, entry[0])) { + return true; + } + } + + return false; + } + + remove(key: K) { + const length = this.#list.length; + + for (let i = 0; i < length; i++) { + if (this.#keyComparer(key, this.#list[i][0])) { + const removed = this.#list.splice(i, 1); + return removed[0][1]; + } + } + + return undefined; + } + + clear() { + this.#list.length = 0; + } + + [Symbol.iterator]() { + return this.#list[Symbol.iterator](); + } +} + +export function createEqualityMap(keyComparer?: Equater): EqualityMap { + return keyComparer ? new CustomEqualityMap(keyComparer) : new NativeEqualityMap(); +} + +export interface AsyncEqualityMap extends AsyncIterable<[K, V]> { + get(key: K): Promise; + set(key: K, value: V): Promise; + contains(key: K): Promise; + remove(key: K): Promise; + clear(): Promise; +} + +class NativeAsyncEqualityMap implements AsyncEqualityMap { + readonly #map = new Map(); + + async get(key: K) { + return this.#map.get(key); + } + + async set(key: K, value: V) { + const existing = await this.get(key); + this.#map.set(key, value); + return existing; + } + + async contains(key: K) { + return this.#map.has(key); + } + + async remove(key: K) { + const existing = await this.get(key); + this.#map.delete(key); + return existing; + } + + async clear() { + this.#map.clear(); + } + + [Symbol.asyncIterator]() { + return asAsyncGenerator(this.#map[Symbol.iterator]()); + } +} + +class CustomAsyncEqualityMap implements AsyncEqualityMap { + readonly #keyComparer: MaybeAsyncEquater; + readonly #list = new Array<[K, V]>(); + + constructor(keyComparer: MaybeAsyncEquater) { + this.#keyComparer = keyComparer; + } + + async get(key: K) { + for (const entry of this.#list) { + if (await this.#keyComparer(key, entry[0])) { + return entry[1]; + } + } + + return undefined; + } + + async set(key: K, value: V) { + for (const entry of this.#list) { + if (await this.#keyComparer(key, entry[0])) { + const previous = entry[1]; + entry[1] = value; + return previous; + } + } + + this.#list.push([key, value]); + + return undefined; + } + + async contains(key: K) { + for (const entry of this.#list) { + if (await this.#keyComparer(key, entry[0])) { + return true; + } + } + + return false; + } + + async remove(key: K) { + const length = this.#list.length; + + for (let i = 0; i < length; i++) { + if (await this.#keyComparer(key, this.#list[i][0])) { + const removed = this.#list.splice(i, 1); + return removed[0][1]; + } + } + + return undefined; + } + + async clear() { + this.#list.length = 0; + } + + [Symbol.asyncIterator]() { + return asAsyncGenerator(this.#list[Symbol.iterator]()); + } +} + +export function createAsyncEqualityMap(keyComparer?: MaybeAsyncEquater): AsyncEqualityMap { + return keyComparer ? new CustomAsyncEqualityMap(keyComparer) : new NativeAsyncEqualityMap(); +} diff --git a/src/equality-set.ts b/src/equality-set.ts new file mode 100644 index 0000000..e8c378b --- /dev/null +++ b/src/equality-set.ts @@ -0,0 +1,195 @@ +import { Equater } from "./sync.js"; +import { MaybeAsyncEquater } from "./async.js"; +import { asAsyncGenerator } from "./utils.js"; + +export interface EqualitySet extends Iterable { + readonly size: number; + add(obj: T): boolean; + contains(obj: T): boolean; + remove(obj: T): boolean; + clear(): void; +} + +class NativeEqualitySet implements EqualitySet { + readonly #set = new Set(); + + get size() { + return this.#set.size; + } + + add(obj: T) { + const exists = this.contains(obj); + this.#set.add(obj); + return !exists; + } + + contains(obj: T) { + return this.#set.has(obj); + } + + remove(obj: T) { + return this.#set.delete(obj); + } + + clear() { + this.#set.clear(); + } + + [Symbol.iterator]() { + return this.#set[Symbol.iterator](); + } +} + +class CustomEqualitySet implements EqualitySet { + readonly #equater: Equater; + readonly #list: T[] = []; + + constructor(equater: Equater) { + this.#equater = equater; + } + + get size() { + return this.#list.length; + } + + add(obj: T) { + if (this.contains(obj)) { + return false; + } + + this.#list.push(obj); + + return true; + } + + contains(obj: T) { + for (const val of this.#list) { + if (this.#equater(obj, val)) { + return true; + } + } + + return false; + } + + remove(obj: T) { + const length = this.#list.length; + + for (let i = 0; i < length; i++) { + if (this.#equater(obj, this.#list[i])) { + this.#list.splice(i, 1); + return true; + } + } + + return false; + } + + clear() { + this.#list.length = 0; + } + + [Symbol.iterator]() { + return this.#list[Symbol.iterator](); + } +} + +export function createEqualitySet(equater?: Equater): EqualitySet { + return equater ? new CustomEqualitySet(equater) : new NativeEqualitySet(); +} + +export interface AsyncEqualitySet extends AsyncIterable { + readonly size: number; + add(obj: T): Promise; + contains(obj: T): Promise; + remove(obj: T): Promise; + clear(): Promise; +} + +class NativeAsyncEqualitySet implements AsyncEqualitySet { + readonly #set = new Set(); + + get size() { + return this.#set.size; + } + + async add(obj: T) { + const exists = await this.contains(obj); + this.#set.add(obj); + return !exists; + } + + async contains(obj: T) { + return this.#set.has(obj); + } + + async remove(obj: T) { + return this.#set.delete(obj); + } + + async clear() { + this.#set.clear(); + } + + [Symbol.asyncIterator]() { + return asAsyncGenerator(this.#set[Symbol.iterator]()); + } +} + +class CustomAsyncEqualitySet implements AsyncEqualitySet { + readonly #equater: MaybeAsyncEquater; + readonly #list: T[] = []; + + constructor(equater: MaybeAsyncEquater) { + this.#equater = equater; + } + + get size() { + return this.#list.length; + } + + async add(obj: T) { + if (await this.contains(obj)) { + return false; + } + + this.#list.push(obj); + + return true; + } + + async contains(obj: T) { + for (const val of this.#list) { + if (await this.#equater(obj, val)) { + return true; + } + } + + return false; + } + + async remove(obj: T) { + const length = this.#list.length; + + for (let i = 0; i < length; i++) { + if (await this.#equater(obj, this.#list[i])) { + this.#list.splice(i, 1); + return true; + } + } + + return false; + } + + async clear() { + this.#list.length = 0; + } + + [Symbol.asyncIterator]() { + return asAsyncGenerator(this.#list[Symbol.iterator]()); + } +} + +export function createAsyncEqualitySet(equater?: MaybeAsyncEquater): AsyncEqualitySet { + return equater ? new CustomAsyncEqualitySet(equater) : new NativeAsyncEqualitySet(); +} diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..f05852a --- /dev/null +++ b/src/index.ts @@ -0,0 +1,4 @@ +export * from "./sync.js"; +export * as async from "./async.js"; +export * as collectors from "./collector.js"; +export * as random from "./random.js"; \ No newline at end of file diff --git a/src/queue.ts b/src/queue.ts new file mode 100644 index 0000000..f89bf2f --- /dev/null +++ b/src/queue.ts @@ -0,0 +1,204 @@ +export interface Queue extends Iterable { + get capacity(): number; + get length(): number; + get free(): number; + + isEmpty(): boolean; + isFull(): boolean; + + enqueue(obj: T): void; + dequeue(): T | undefined; + + clear(): void; +} + +class EmptyQueue implements Queue { + get capacity() { + return 0; + } + + get length() { + return 0; + } + + get free() { + return 0; + } + + isEmpty() { + return true; + } + + isFull() { + return true; + } + + enqueue() { } + + dequeue(): T | undefined { + return undefined; + } + + clear() { } + + *[Symbol.iterator]() { } +} + +class BoundedArrayQueue implements Queue { + readonly #capacity: number; + readonly #buffer: T[]; + #addIndex = 0; + #getIndex = 0; + + constructor(capacity: number) { + this.#capacity = capacity; + this.#buffer = new Array(capacity); + } + + get capacity() { + return this.#capacity; + } + + get length() { + const n = this.#diff(); + return n < 0 ? n + this.#capacity : n; + } + + get free() { + return this.#capacity - this.length; + } + + isEmpty() { + return this.#getIndex === this.#addIndex; + } + + isFull() { + return this.#diff() === 1; + } + + #diff() { + return this.#offset(this.#addIndex, -this.#getIndex); + } + + #nextIndex(index: number) { + return this.#offset(index, 1); + } + + #offset(index: number, offset: number) { + return (index + offset) % this.#capacity; + } + + #enqueue(obj: T) { + this.#buffer[this.#addIndex] = obj; + this.#addIndex = this.#nextIndex(this.#addIndex); + } + + enqueue(obj: T) { + this.#enqueue(obj); + + if (this.isEmpty()) { + this.#getIndex = this.#nextIndex(this.#getIndex); + } + } + + #dequeue() { + const obj = this.#buffer[this.#getIndex]; + delete this.#buffer[this.#getIndex]; + this.#getIndex = this.#nextIndex(this.#getIndex); + return obj; + } + + dequeue() { + if (this.isEmpty()) { + return undefined; + } + + return this.#dequeue(); + } + + clear() { + this.#buffer.length = 0; + this.#addIndex = 0; + this.#getIndex = 0; + } + + *[Symbol.iterator]() { + while (!this.isEmpty()) { + yield this.#dequeue(); + } + } +} + +class UnboundedArrayQueue implements Queue { + readonly #buffer: T[] = []; + #addIndex = 0; + #getIndex = 0; + + get capacity() { + return Infinity; + } + + get length() { + return this.#addIndex - this.#getIndex; + } + + get free() { + return Infinity; + } + + isEmpty() { + return this.#getIndex === this.#addIndex; + } + + isFull() { + return false; + } + + enqueue(obj: T) { + this.#buffer[this.#addIndex] = obj; + this.#addIndex++; + } + + dequeue() { + if (this.isEmpty()) { + return undefined; + } + + const obj = this.#buffer[this.#getIndex]; + delete this.#buffer[this.#getIndex]; + this.#getIndex++; + return obj; + } + + clear() { + this.#buffer.length = 0; + this.#addIndex = 0; + this.#getIndex = 0; + } + + *[Symbol.iterator]() { + for (let i = this.#getIndex; i < this.#addIndex; i++) { + yield this.#buffer[i]; + } + + this.clear(); + } +} + +const emptyQueue = new EmptyQueue(); + +export function createQueue(capacity?: number): Queue { + if (capacity === undefined) { + return new UnboundedArrayQueue(); + } + + if (capacity < 0) { + throw new Error("Capacity must be greater than or equal to zero."); + } + + if (capacity === 0) { + return emptyQueue; + } + + return new BoundedArrayQueue(capacity); +} \ No newline at end of file diff --git a/src/random.ts b/src/random.ts new file mode 100644 index 0000000..9d62f33 --- /dev/null +++ b/src/random.ts @@ -0,0 +1,137 @@ +import { BitArray } from "./bitarray.js"; +import { asArray } from "./utils.js"; + +export interface ElementPredicate { + (index: number, obj: T): boolean; +} + +export interface ElementWeight { + (index: number, obj: T): number; +} + +export interface RandomGenerator { + (): number; +} + +export interface RandomOptions { + predicate?: ElementPredicate; + weight?: ElementWeight; + random?: RandomGenerator; +}; + +export const alwaysTrue: ElementPredicate = () => true; +export const weightOfOne: ElementWeight = () => 1.0; +export const mathRandom: RandomGenerator = () => Math.random(); + +const defaultOptions = Object.freeze>>({ + predicate: alwaysTrue, + weight: weightOfOne, + random: mathRandom +}); + +function mergeOptions(first: RandomOptions | undefined, second: RandomOptions | undefined): RandomOptions | undefined { + if (!first) { + return second; + } + + if (!second) { + return first; + } + + const firstPredicate = first.predicate; + const secondPredicate = second.predicate; + + return { + predicate: firstPredicate ? secondPredicate ? (i, o) => firstPredicate(i, o) && secondPredicate(i, o) : firstPredicate : secondPredicate, + weight: first.weight ?? second.weight, + random: first.random ?? second.random + }; +} + +function withDefaultOptions(options: RandomOptions | undefined): Required> { + if (!options) { + return { ...defaultOptions }; + } + + return { + predicate: options.predicate ?? defaultOptions.predicate, + weight: options.weight ?? defaultOptions.weight, + random: options.random ?? defaultOptions.random + }; +} + +export function getRandomElement(sequence: Iterable, options?: RandomOptions) { + const { predicate, weight, random } = withDefaultOptions(options); + + let result: T | undefined = undefined; + let resultIndex = -1; + let index = 0; + let weightAcc = 0.0; + + for (const element of sequence) { + const currentIndex = index++; + + if (predicate(currentIndex, element)) { + const w = weight(currentIndex, element); + + if (w <= 0.0) { + continue; + } + + weightAcc += w; + + if (random() * weightAcc < w) { + result = element; + resultIndex = currentIndex; + } + } + } + + return { element: result, index: resultIndex }; +} + +export class RandomPicker { + readonly #elements: Iterable; + readonly #flags: BitArray; + readonly #options: RandomOptions; + + public constructor(elements: T[], options?: RandomOptions) { + this.#elements = elements; + this.#flags = new BitArray(elements.length); + this.#options = withDefaultOptions(mergeOptions({ predicate: (i, o) => this.#flags.get(i) }, options)); + + this.reset(); + } + + public static from(iterable: Iterable, options?: RandomOptions) { + return new this(asArray(iterable), options); + } + + public static of(options?: RandomOptions, ...values: T[]) { + return new this(values, options); + } + + public get state() { + return this.#flags; + } + + public reset() { + this.#flags.fill(true); + } + + public next() { + const result = getRandomElement(this.#elements, this.#options); + + if (result.index < 0) { + this.reset(); + } else { + this.#flags.set(result.index, false); + + if (this.#flags.isEmpty()) { + this.reset(); + } + } + + return result.element; + } +} diff --git a/src/sorting.ts b/src/sorting.ts new file mode 100644 index 0000000..a0af305 --- /dev/null +++ b/src/sorting.ts @@ -0,0 +1,88 @@ +import { MaybeAsyncComparer } from "./async.js"; +import { reverseAsyncComparer } from "./utils.js"; + +export interface AsyncSorter { + sort(array: T[], descending: boolean, comparer: MaybeAsyncComparer): Promise; +} + +function swap(array: any[], i: number, j: number) { + const tmp = array[i]; + array[i] = array[j]; + array[j] = tmp; +} + +abstract class BaseAsyncSorter implements AsyncSorter { + async sort(array: T[], descending: boolean, comparer: MaybeAsyncComparer) { + await this._sort(array, descending ? reverseAsyncComparer(comparer) : comparer); + } + + protected abstract _sort(array: T[], comparer: MaybeAsyncComparer): Promise; +} + +class InsertionSorter extends BaseAsyncSorter { + protected override async _sort(array: T[], comparer: MaybeAsyncComparer) { + for (let i = 1; i < array.length; i++) { + const obj = array[i]; + + for (let j = i - 1; j >= 0; j--) { + if (await comparer(obj, array[j]) < 0) { + for (let k = i; k > j; k--) { + swap(array, k - 1, k); + } + + break; + } + } + } + } +} + +class SelectionSorter extends BaseAsyncSorter { + protected async _sort(array: T[], comparer: MaybeAsyncComparer) { + for (let i = 0; i < array.length; i++) { + let smallest = array[i]; + let smallestIndex = i; + + for (let j = i; j < array.length; j++) { + const current = array[j]; + + if (await comparer(current, smallest) < 0) { + smallest = current; + smallestIndex = j; + } + } + + if (smallestIndex !== i) { + swap(array, smallestIndex, i); + } + } + } +} + +class BubbleSorter extends BaseAsyncSorter { + protected async _sort(array: T[], comparer: MaybeAsyncComparer) { + const length = array.length; + + for (let k = length - 2; k >= 0; k--) { + let hasSwaped = false; + + for (let i = 0; i <= k; i++) { + const j = i + 1; + const a = array[i], b = array[j]; + + if (await comparer(a, b) > 0) { + swap(array, i, j); + hasSwaped = true; + } + } + + if (!hasSwaped) { + break; + } + } + } +} + +export const insertionSorter: AsyncSorter = new InsertionSorter(); +export const selectionSorter: AsyncSorter = new SelectionSorter(); +export const bubbleSorter: AsyncSorter = new BubbleSorter(); diff --git a/src/sync.ts b/src/sync.ts new file mode 100644 index 0000000..32a2ae1 --- /dev/null +++ b/src/sync.ts @@ -0,0 +1,2689 @@ +import { createEqualitySet } from "./equality-set.js"; +import { createEqualityMap } from "./equality-map.js"; +import { RandomGenerator, RandomOptions, getRandomElement, mathRandom } from "./random.js"; +import { createQueue } from "./queue.js"; +import { Collector } from "./collector.js"; +import { combineComparers, defaultArrayComparer, identity, operatorCompare, reverseComparer, strictEquals, wrapAsIterable } from "./utils.js"; + +export type Predicate = (obj: T) => boolean; +export type FilterPredicate = (obj: TElement) => obj is TFiltered; +export type Converter = (obj: TFrom) => TTo; +export type BiConverter = (first: TFromFirst, second: TFromSecond) => TTo; +export type Action = (obj: T) => void; +export type Accumulator = (acc: TAccumulator, obj: TElement) => TAccumulator; +export type Comparer = (first: T, second: T) => number; +export type Equater = (first: T, second: T) => boolean; + +export interface Enumerable extends Iterable { + iterator(): Iterator; + + apply(pipeline: (enumerable: Enumerable) => TResult): TResult; + + count(predicate?: Predicate): number; + nonEnumeratedCount(): number; + fastCount(): number; + maxCount(): number; + + select(selector: Converter): Enumerable; + selectMany(selector: Converter>): Enumerable; + + where(predicate: FilterPredicate): Enumerable; + where(predicate: Predicate): Enumerable; + + groupBy(keySelector: Converter, elementSelector?: undefined, keyComparer?: Equater): Enumerable>; + groupBy(keySelector: Converter, elementSelector: Converter, keyComparer?: Equater): Enumerable>; + + join(iterable: Iterable, firstKeySelector: Converter, secondKeySelector: Converter, resultSelector?: undefined, keyComparer?: Equater): Enumerable<[TElement, TOther]>; + join(iterable: Iterable, firstKeySelector: Converter, secondKeySelector: Converter, resultSelector: BiConverter, keyComparer?: Equater): Enumerable; + + groupJoin(iterable: Iterable, firstKeySelector: Converter, secondKeySelector: Converter, resultSelector?: undefined, keyComparer?: Equater): Enumerable>; + groupJoin(iterable: Iterable, firstKeySelector: Converter, secondKeySelector: Converter, resultSelector: BiConverter, TResult>, keyComparer?: Equater): Enumerable; + + contains(obj: TElement, equater?: Equater): boolean; + + equals(iterable: Iterable, equater?: Equater): boolean; + + append(obj: TElement): Enumerable; + + prepend(obj: TElement): Enumerable; + + remove(obj: TElement, all?: boolean, equater?: Equater): Enumerable; + + concat(...iterables: Iterable[]): Enumerable; + + first(predicate?: Predicate): TElement; + firstOrDefault(predicate?: Predicate, def?: TElement): TElement | undefined; + + last(predicate?: Predicate): TElement; + lastOrDefault(predicate?: Predicate, def?: TElement): TElement | undefined; + + single(predicate?: Predicate): TElement; + singleOrDefault(predicate?: Predicate, def?: TElement): TElement | undefined; + + elementAt(index: number): TElement; + elementAtOrDefault(index: number, def?: TElement): TElement | undefined; + + aggregate(accumulator: Accumulator): TElement; + aggregate(accumulator: Accumulator, seed?: TAccumulator): TAccumulator; + aggregate(accumulator: Accumulator, seed?: TAccumulator, resultSelector?: Converter): TResult; + + min(comparer?: Comparer): TElement; + minBy(selector: Converter, comparer?: Comparer): TElement; + + max(comparer?: Comparer): TElement; + maxBy(selector: Converter, comparer?: Comparer): TElement; + + order(comparer?: Comparer): OrderedEnumerable; + orderBy(selector: Converter, comparer?: Comparer): OrderedEnumerable; + + orderDescending(comparer?: Comparer): OrderedEnumerable; + orderByDescending(selector: Converter, comparer?: Comparer): OrderedEnumerable; + + distinct(equater?: Equater): Enumerable; + distinctBy(selector: Converter, equater?: Equater): Enumerable; + + union(iterable: Iterable, equater?: Equater): Enumerable; + unionBy(iterable: Iterable, selector: Converter, equater?: Equater): Enumerable; + + except(iterable: Iterable, equater?: Equater): Enumerable; + exceptBy(iterable: Iterable, selector: Converter, equater?: Equater): Enumerable; + + intersect(iterable: Iterable, equater?: Equater): Enumerable; + intersectBy(iterable: Iterable, selector: Converter, equater?: Equater): Enumerable; + + all(predicate: Predicate): boolean; + any(predicate: Predicate): boolean; + any(): boolean; + none(predicate: Predicate): boolean; + none(): boolean; + + skip(n: number): Enumerable; + skipLast(n: number): Enumerable; + skipWhile(condition: Predicate): Enumerable; + + take(n: number): Enumerable; + takeLast(n: number): Enumerable; + takeWhile(condition: Predicate): Enumerable; + + peek(action: Action): Enumerable; + + forEach(action: Action): void; + + zip(iterable: Iterable): Enumerable<[TElement, TOther]>; + + indexed(): Enumerable<[number, TElement]>; + + reversed(): Enumerable; + + chunked(size: number): Enumerable; + + random(options?: RandomOptions): TElement | undefined; + + cached(): Enumerable; + + asArray(): TElement[]; + toArray(): TElement[]; + toMap(keySelector: Converter, valueSelector: Converter): Map; + toSet(): Set; + toObject(keySelector: Converter, valueSelector: Converter): Record; + + collect(collector: Collector): TResult; +} + +export interface GroupedEnumerable extends Enumerable { + get key(): TKey; +} + +export interface OrderedEnumerable extends Enumerable { + get comparer(): Comparer | undefined; + + thenSelf(comparer?: Comparer): OrderedEnumerable; + + thenBy(selector: Converter, comparer?: Comparer): OrderedEnumerable; + + thenSelfDescending(comparer?: Comparer): OrderedEnumerable; + + thenByDescending(selector: Converter, comparer?: Comparer): OrderedEnumerable; +} + +export function wrap(iterable: Iterable): Enumerable { + if (isEnumerable(iterable)) { + return iterable; + } + + if (Array.isArray(iterable)) { + return array(iterable); + } + + if (iterable instanceof Set) { + return set(iterable); + } + + if (iterable instanceof Map) { + return map(iterable) as unknown as Enumerable; + } + + return sequence(iterable); +} + +export function sequence(iterable: Iterable): Enumerable { + return new WrappedIterable(iterable); +} + +export function empty(): Enumerable { + return EmptyEnumerable.INSTANCE; +} + +export function single(obj: T): Enumerable { + return new WrappedObject(obj); +} + +export function array(array: T[]): Enumerable { + return new WrappedArray(array); +} + +export function arrayLike(arrayLike: ArrayLike): Enumerable { + return new WrappedArrayLike(arrayLike); +} + +export function set(set: Set): Enumerable { + return new WrappedSet(set); +} + +export function map(map: Map): Enumerable<[K, V]> { + return new WrappedMap(map); +} + +export function of(...elements: T[]): Enumerable { + switch (elements.length) { + case 0: + return empty(); + case 1: + return single(elements[0]); + default: + return array(elements); + } +} + +export function ofPropertyKeys(...elements: T[]): Enumerable { + return of(...elements); +} + +export function entries(o: Record | ArrayLike): Enumerable<[string, T]> { + return array(Object.entries(o)); +} + +export function keys(o: object): Enumerable { + return array(Object.keys(o)); +} + +export function func(f: () => T): Enumerable { + return new FunctionEnumerable(f); +} + +export function generator(generator: () => Iterable): Enumerable { + return new GeneratorEnumerable(generator); +} + +export function range(max: number): Enumerable +export function range(min: number, max: number): Enumerable +export function range(min: number, max: number, step: number): Enumerable +export function range(a: number, b?: number, c?: number): Enumerable { + if (b === undefined) { + b = a; + a = 0; + } + + if (c === undefined) { + c = 1; + } + + return new RangeEnumerable(a, b, c); +} + +export function bigintRange(max: bigint): Enumerable +export function bigintRange(min: bigint, max: bigint): Enumerable +export function bigintRange(min: bigint, max: bigint, step: bigint): Enumerable +export function bigintRange(a: bigint, b?: bigint, c?: bigint): Enumerable { + if (b === undefined) { + b = a; + a = 0n; + } + + if (c === undefined) { + c = 1n; + } + + return new BigIntRangeEnumerable(a, b, c); +} + +export function repeat(value: T, count?: number): Enumerable { + if (count == undefined) { + return new RepeatForeverEnumerable(value); + } + + if (count < 0) { + throw new RangeError(); + } + + if (count === 0) { + return empty(); + } + + if (count === 1) { + return new WrappedObject(value); + } + + return new RepeatEnumerable(value, count); +} + +export function randomSequence(random?: RandomGenerator): Enumerable { + return new FunctionEnumerable(random ?? mathRandom); +} + +export function concat(...enumerables: Enumerable[]): Enumerable { + return new ConcatEnumerable(enumerables); +} + +export function isEnumerable(obj: any): obj is Enumerable { + return obj instanceof EnumerableMarker; +} + +//#region enumerable implementation + +class EnumerableMarker { + +} + +abstract class BaseEnumerable extends EnumerableMarker implements Enumerable { + [Symbol.iterator]() { + return this.iterator(); + } + + abstract iterator(): Iterator; + + apply(pipeline: (enumerable: this) => TResult) { + return pipeline(this); + } + + select(converter: Converter): Enumerable { + return new SelectEnumerable(this, converter); + } + + selectMany(converter: Converter>): Enumerable { + return new SelectManyEnumerable(this, converter); + } + + where(predicate: FilterPredicate): Enumerable { + return new WhereEnumerable(this, predicate); + } + + groupBy(keySelector: Converter, elementSelector?: Converter, keyComparer?: Equater): Enumerable> { + return new GroupByEnumerable(this, keySelector, elementSelector, keyComparer); + } + + join(iterable: Iterable, firstKeySelector: Converter, secondKeySelector: Converter, resultSelector?: BiConverter, keyComparer?: Equater): Enumerable { + return new JoinEnumerable(this, wrap(iterable), firstKeySelector, secondKeySelector, resultSelector, keyComparer); + } + + groupJoin(iterable: Iterable, firstKeySelector: Converter, secondKeySelector: Converter, resultSelector?: BiConverter, TResult>, keyComparer?: Equater): Enumerable { + return new GroupJoinEnumerable(this, wrap(iterable), firstKeySelector, secondKeySelector, resultSelector, keyComparer); + } + + contains(obj: TElement, equater?: Equater) { + if (!equater) { + equater = strictEquals; + } + + for (const element of this) { + if (equater(element, obj)) { + return true; + } + } + + return false; + } + + equals(iterable: Iterable, equater?: Equater) { + const that = wrap(iterable); + + const thisCount = this.nonEnumeratedCount(); + const thatCount = that.nonEnumeratedCount(); + + if (thisCount >= 0 && thatCount >= 0 && thisCount !== thatCount) { + return false; + } + + if (!equater) { + equater = strictEquals; + } + + const thisIterator = this.iterator(); + const thatIterator = that.iterator(); + + while (true) { + const thisNext = thisIterator.next(); + const thatNext = thatIterator.next(); + + if (thisNext.done) { + return thatNext.done === true; + } + + if (thatNext.done) { + return false; + } + + if (!equater(thisNext.value, thatNext.value)) { + return false; + } + } + } + + append(obj: TElement): Enumerable { + return new AppendEnumerable(this, obj); + } + + prepend(obj: TElement): Enumerable { + return new PrependEnumerable(this, obj); + } + + remove(obj: TElement, all?: boolean, equater?: Equater): Enumerable { + return new RemoveEnumerable(this, obj, all, equater); + } + + concat(...iterables: Iterable[]) { + if (iterables.length === 0) { + return this; + } + + const arr = new Array>(); + arr.push(this); + + for (const iterable of iterables) { + arr.push(wrap(iterable)); + } + + return new ConcatEnumerable(arr); + } + + count(predicate?: Predicate) { + let count = 0; + + if (predicate) { + for (const element of this) { + if (predicate(element)) { + count++; + } + } + } else { + const iterator = this.iterator(); + + while (!iterator.next().done) { + count++; + } + } + + return count; + } + + nonEnumeratedCount() { + return -1; + } + + fastCount() { + const n = this.nonEnumeratedCount(); + return n >= 0 ? n : this.count(); + } + + maxCount() { + const n = this.nonEnumeratedCount(); + return n >= 0 ? n : Infinity; + } + + #tryGetFirst(predicate?: Predicate): { found: boolean, element?: TElement | undefined } { + if (predicate) { + for (const element of this) { + if (predicate(element)) { + return { + found: true, + element + }; + } + } + } else { + const next = this.iterator().next(); + + if (!next.done) { + return { + found: true, + element: next.value + }; + } + } + + return { + found: false + }; + } + + first(predicate?: Predicate) { + const result = this.#tryGetFirst(predicate); + + if (result.found) { + return result.element!; + } + + throw new Error("No element was found."); + } + + firstOrDefault(predicate?: Predicate, def?: TElement) { + const result = this.#tryGetFirst(predicate); + + return result.found ? result.element : def; + } + + #tryGetLast(predicate?: Predicate): { found: boolean, element?: TElement } { + let found = false; + let result: TElement | undefined = undefined; + + if (predicate) { + for (const element of this) { + if (predicate(element)) { + found = true; + result = element; + } + } + } else { + for (const element of this) { + found = true; + result = element; + } + } + + return { + found, + element: result + }; + } + + last(predicate?: Predicate) { + const result = this.#tryGetLast(predicate); + + if (result.found) { + return result.element!; + } + + throw new Error("No element was found."); + } + + lastOrDefault(predicate?: Predicate, def?: TElement) { + const result = this.#tryGetLast(predicate); + + return result.found ? result.element : def; + } + + #tryGetSingle(predicate?: Predicate): { found: boolean, element?: TElement, reason?: number } { + if (predicate) { + let result: { found: boolean; element: TElement; } | undefined = undefined; + + for (const element of this) { + if (predicate(element)) { + if (result) { + return { + found: false, + reason: 2 + }; + } + + result = { + found: true, + element + }; + } + } + } else { + const iterator = this.iterator(); + let next = iterator.next(); + + if (!next.done) { + const result = { + found: true, + element: next.value + }; + + next = iterator.next(); + + if (next.done) { + return result; + } + + return { + found: false, + reason: 2 + }; + } + } + + return { + found: false, + reason: 1 + }; + } + + single(predicate?: Predicate) { + const result = this.#tryGetSingle(predicate); + + if (result.found) { + return result.element!; + } + + let reason: string; + switch (result.reason!) { + case 1: + reason = "No element was found."; + break; + case 2: + reason = "More than one element was found."; + break; + default: + reason = ""; + } + throw new Error(reason); + } + + singleOrDefault(predicate?: Predicate, def?: TElement) { + const result = this.#tryGetSingle(predicate); + + return result.found ? result.element : def; + } + + #tryElementAt(index: number) { + let i = index; + + for (const element of this) { + if (i === 0) { + return { + found: true, + element + } as const; + } + + i--; + } + + return { + found: false + } as const; + } + + elementAt(index: number) { + const result = this.#tryElementAt(index); + + if (result.found) { + return result.element; + } + + throw new Error("No element found at given index."); + } + + elementAtOrDefault(index: number, def?: TElement) { + const result = this.#tryElementAt(index); + + return result.found ? result.element : def; + } + + aggregate(accumulator: Accumulator, seed?: TAccumulator, resultSelector?: Converter) { + const iterator = this.iterator(); + + if (seed === undefined) { + const next = iterator.next(); + + if (next.done) { + throw new Error("Aggregation requires at least one element."); + } + + seed = next.value as unknown as TAccumulator; + } + + let acc = seed; + + while (true) { + const next = iterator.next(); + + if (next.done) { + break; + } + + acc = accumulator(acc, next.value); + } + + if (resultSelector) { + return resultSelector(acc); + } + + return acc as unknown as TResult; + } + + #find(sorter: Predicate, selector?: Converter, comparer?: Comparer) { + const iterator = this.iterator(); + + let next = iterator.next(); + + if (next.done) { + throw new Error("Sequence contains no element.") + } + + if (!selector) { + selector = identity as Converter; + } + + if (!comparer) { + comparer = operatorCompare; + } + + let result = next.value; + let convertedResult = selector(result); + + while (true) { + next = iterator.next(); + + if (next.done) { + break; + } + + const value = next.value; + const convertedValue = selector(value); + + if (sorter(comparer(convertedResult, convertedValue))) { + result = value; + convertedResult = convertedValue; + } + } + + return result; + } + + min(comparer?: Comparer) { + return this.#find(x => x > 0, undefined, comparer); + } + + minBy(converter: Converter, comparer?: Comparer) { + return this.#find(x => x > 0, converter, comparer); + } + + max(comparer?: Comparer) { + return this.#find(x => x < 0, undefined, comparer); + } + + maxBy(converter: Converter, comparer?: Comparer) { + return this.#find(x => x < 0, converter, comparer); + } + + order(comparer?: Comparer): OrderedEnumerable { + return new OrderEnumerable(this, false, comparer); + } + + orderBy(selector: Converter, comparer?: Comparer): OrderedEnumerable { + return new OrderByEnumerable(this, false, selector, comparer); + } + + orderDescending(comparer?: Comparer): OrderedEnumerable { + return new OrderEnumerable(this, true, comparer); + } + + orderByDescending(selector: Converter, comparer?: Comparer): OrderedEnumerable { + return new OrderByEnumerable(this, true, selector, comparer); + } + + distinct(equater?: Equater): Enumerable { + return new DistinctEnumerable(this, equater); + } + + distinctBy(selector: Converter, equater?: Equater): Enumerable { + return new DistinctByEnumerable(this, selector, equater); + } + + union(iterable: Iterable, equater?: Equater): Enumerable { + return new UnionEnumerable(this, wrap(iterable), equater); + } + + unionBy(iterable: Iterable, selector: Converter, equater?: Equater): Enumerable { + return new UnionByEnumerable(this, wrap(iterable), selector, equater); + } + + except(iterable: Iterable): Enumerable { + return new ExceptEnumerable(this, wrap(iterable)); + } + + exceptBy(iterable: Iterable, selector: Converter): Enumerable { + return new ExceptByEnumerable(this, wrap(iterable), selector); + } + + intersect(iterable: Iterable): Enumerable { + return new IntersectEnumerable(this, wrap(iterable)); + } + + intersectBy(iterable: Iterable, selector: Converter): Enumerable { + return new IntersectByEnumerable(this, wrap(iterable), selector); + } + + all(predicate: Predicate) { + const n = this.nonEnumeratedCount(); + + if (n === 0) { + return false; + } + + for (const element of this) { + if (!predicate(element)) { + return false; + } + } + + return true; + } + + any(predicate?: Predicate) { + const n = this.nonEnumeratedCount(); + + if (n === 0) { + return false; + } + + if (predicate) { + for (const element of this) { + if (predicate(element)) { + return true; + } + } + + return false; + } + + return n < 0 ? !this.iterator().next().done : n > 0; + } + + none(predicate?: Predicate) { + const n = this.nonEnumeratedCount(); + + if (n === 0) { + return true; + } + + if (predicate) { + for (const element of this) { + if (predicate(element)) { + return false; + } + } + + return true; + } + + return n < 0 && this.iterator().next().done === true; + } + + skip(n: number): Enumerable { + if (n < 0) { + throw new Error("Cannot skip a negative number of elements."); + } + + return n === 0 ? this : new SkipEnumerable(this, n); + } + + skipLast(n: number): Enumerable { + if (n < 0) { + throw new Error("Cannot skip a negative number of elements."); + } + + return n === 0 ? this : new SkipLastEnumerable(this, n); + } + + skipWhile(predicate: Predicate): Enumerable { + return new SkipWhileEnumerable(this, predicate); + } + + take(n: number): Enumerable { + if (n < 0) { + throw new Error("Cannot take a negative number of elements."); + } + + return n === 0 ? empty() : new TakeEnumerable(this, n); + } + + takeLast(n: number): Enumerable { + if (n < 0) { + throw new Error("Cannot take a negative number of elements."); + } + + return n === 0 ? empty() : new TakeLastEnumerable(this, n); + } + + takeWhile(predicate: Predicate): Enumerable { + return new TakeWhileEnumerable(this, predicate); + } + + peek(action: Action): Enumerable { + return new PeekEnumerable(this, action); + } + + forEach(action: Action) { + for (const element of this) { + action(element); + } + } + + zip(iterable: Iterable): Enumerable<[TElement, TOther]> { + return new ZippedEnumerable(this, wrap(iterable)); + } + + indexed(): Enumerable<[number, TElement]> { + return new IndexedEnumerable(this); + } + + reversed(): Enumerable { + return new ReversedEnumerable(this); + } + + chunked(size: number): Enumerable { + if (size <= 0) { + throw new Error("Chunk size must be positive."); + } + + return new ChunkedEnumerable(this, size); + } + + random(options?: RandomOptions) { + return getRandomElement(this, options).element; + } + + cached(): Enumerable { + return new CacheEnumerable(this); + } + + asArray() { + return this.toArray(); + } + + toArray() { + return Array.from(this); + } + + toMap(keySelector: Converter, valueSelector: Converter) { + const map = new Map(); + + for (const element of this) { + const key = keySelector(element); + const value = valueSelector(element); + + map.set(key, value); + } + + return map; + } + + toSet() { + return new Set(this); + } + + toObject(keySelector: Converter, valueSelector: Converter) { + const obj: Record = {}; + + for (const element of this) { + const key = keySelector(element); + const value = valueSelector(element); + + obj[key] = value; + } + + return obj; + } + + collect(collector: Collector) { + const acc = collector.initialize(); + + for (const e of this) { + collector.accumulate(acc, e); + } + + return collector.finalize(acc); + } +} + +class DelegatedEnumerable extends EnumerableMarker implements Enumerable { + #enumerable: Enumerable; + + constructor(enumerable: Enumerable) { + super(); + + this.#enumerable = enumerable; + } + + get enumerable() { + return this.#enumerable; + } + + protected set enumerable(value: Enumerable) { + this.#enumerable = value; + } + + [Symbol.iterator]() { + return this.iterator(); + } + + iterator() { + return this.#enumerable.iterator(); + } + + apply(pipeline: (enumerable: Enumerable) => TResult) { + return this.#enumerable.apply(pipeline); + } + + count(predicate?: Predicate) { + return this.#enumerable.count(predicate); + } + + nonEnumeratedCount() { + return this.#enumerable.nonEnumeratedCount(); + } + + fastCount() { + return this.#enumerable.fastCount(); + } + + maxCount() { + return this.#enumerable.maxCount(); + } + + select(selector: Converter) { + return this.#enumerable.select(selector); + } + + selectMany(selector: Converter>) { + return this.#enumerable.selectMany(selector); + } + + where(predicate: FilterPredicate) { + return this.#enumerable.where(predicate); + } + + groupBy(keySelector: Converter, elementSelector?: Converter, keyComparer?: Equater) { + //@ts-ignore + return this.#enumerable.groupBy(keySelector, elementSelector, keyComparer); + } + + join(iterable: Iterable, firstKeySelector: Converter, secondKeySelector: Converter, resultSelector?: BiConverter, keyComparer?: Equater) { + //@ts-ignore + return this.#enumerable.join(iterable, firstKeySelector, secondKeySelector, resultSelector, keyComparer); + } + + groupJoin(iterable: Iterable, firstKeySelector: Converter, secondKeySelector: Converter, resultSelector?: BiConverter, TResult>, keyComparer?: Equater) { + //@ts-ignore + return this.#enumerable.groupJoin(iterable, firstKeySelector, secondKeySelector, resultSelector, keyComparer); + } + + contains(obj: TElement, equater?: Equater) { + return this.#enumerable.contains(obj, equater); + } + + equals(iterable: Iterable, equater?: Equater) { + return this.#enumerable.equals(iterable, equater); + } + + append(obj: TElement) { + return this.#enumerable.append(obj); + } + + prepend(obj: TElement) { + return this.#enumerable.prepend(obj); + } + + remove(obj: TElement, all?: boolean, equater?: Equater) { + return this.#enumerable.remove(obj, all, equater); + } + + concat(...iterables: Iterable[]) { + return this.#enumerable.concat(...iterables); + } + + first(predicate?: Predicate) { + return this.#enumerable.first(predicate); + } + + firstOrDefault(predicate?: Predicate, def?: TElement) { + return this.#enumerable.firstOrDefault(predicate, def); + } + + last(predicate?: Predicate) { + return this.#enumerable.last(predicate); + } + + lastOrDefault(predicate?: Predicate, def?: TElement) { + return this.#enumerable.lastOrDefault(predicate, def); + } + + single(predicate?: Predicate) { + return this.#enumerable.single(predicate); + } + + singleOrDefault(predicate?: Predicate, def?: TElement) { + return this.#enumerable.singleOrDefault(predicate, def); + } + + elementAt(index: number) { + return this.#enumerable.elementAt(index); + } + + elementAtOrDefault(index: number, def?: TElement) { + return this.#enumerable.elementAtOrDefault(index, def); + } + + aggregate(accumulator: Accumulator, seed?: TAccumulator, resultSelector?: Converter) { + return this.#enumerable.aggregate(accumulator, seed, resultSelector); + } + + min(comparer?: Comparer) { + return this.#enumerable.min(comparer); + } + + minBy(selector: Converter, comparer?: Comparer) { + return this.#enumerable.minBy(selector, comparer); + } + + max(comparer?: Comparer) { + return this.#enumerable.max(comparer); + } + + maxBy(selector: Converter, comparer?: Comparer) { + return this.#enumerable.maxBy(selector, comparer); + } + + order(comparer?: Comparer) { + return this.#enumerable.order(comparer); + } + + orderBy(selector: Converter, comparer?: Comparer) { + return this.#enumerable.orderBy(selector, comparer); + } + + orderDescending(comparer?: Comparer) { + return this.#enumerable.orderDescending(comparer); + } + + orderByDescending(selector: Converter, comparer?: Comparer) { + return this.#enumerable.orderByDescending(selector, comparer); + } + + distinct(equater?: Equater) { + return this.#enumerable.distinct(equater); + } + + distinctBy(selector: Converter, equater?: Equater) { + return this.#enumerable.distinctBy(selector, equater); + } + + union(iterable: Iterable, equater?: Equater) { + return this.#enumerable.union(iterable, equater); + } + + unionBy(iterable: Iterable, selector: Converter, equater?: Equater) { + return this.#enumerable.unionBy(iterable, selector, equater); + } + + except(iterable: Iterable, equater?: Equater) { + return this.#enumerable.except(iterable, equater); + } + + exceptBy(iterable: Iterable, selector: Converter, equater?: Equater) { + return this.#enumerable.exceptBy(iterable, selector, equater); + } + + intersect(iterable: Iterable, equater?: Equater) { + return this.#enumerable.intersect(iterable, equater); + } + + intersectBy(iterable: Iterable, selector: Converter, equater?: Equater) { + return this.#enumerable.intersectBy(iterable, selector, equater); + } + + all(predicate: Predicate) { + return this.#enumerable.all(predicate); + } + + any(predicate?: Predicate) { + //@ts-ignore + return this.#enumerable.any(predicate); + } + + none(predicate?: Predicate) { + //@ts-ignore + return this.#enumerable.none(predicate); + } + + skip(n: number) { + return this.#enumerable.skip(n); + } + + skipLast(n: number) { + return this.#enumerable.skipLast(n); + } + + skipWhile(condition: Predicate) { + return this.#enumerable.skipWhile(condition); + } + + take(n: number) { + return this.#enumerable.take(n); + } + + takeLast(n: number) { + return this.#enumerable.takeLast(n); + } + + takeWhile(condition: Predicate) { + return this.#enumerable.takeWhile(condition); + } + + peek(action: Action) { + return this.#enumerable.peek(action); + } + + forEach(action: Action) { + this.#enumerable.forEach(action); + } + + zip(iterable: Iterable) { + return this.#enumerable.zip(iterable); + } + + indexed() { + return this.#enumerable.indexed(); + } + + reversed() { + return this.#enumerable.reversed(); + } + + chunked(size: number) { + return this.#enumerable.chunked(size); + } + + random(options?: RandomOptions) { + return this.#enumerable.random(options); + } + + cached() { + return this.#enumerable.cached(); + } + + asArray() { + return this.#enumerable.asArray(); + } + + toArray() { + return this.#enumerable.toArray(); + } + + toMap(keySelector: Converter, valueSelector: Converter) { + return this.#enumerable.toMap(keySelector, valueSelector); + } + + toSet() { + return this.#enumerable.toSet(); + } + + toObject(keySelector: Converter, valueSelector: Converter) { + return this.#enumerable.toObject(keySelector, valueSelector); + } + + collect(collector: Collector) { + return this.#enumerable.collect(collector); + } +} + +class GroupedEnumerableImpl extends DelegatedEnumerable implements GroupedEnumerable { + readonly #key: TKey; + + constructor(key: TKey, grouping: Enumerable) { + super(grouping); + + this.#key = key; + } + + public get key() { + return this.#key; + } +} + +abstract class BaseOrderedEnumerable extends BaseEnumerable implements OrderedEnumerable { + readonly #enumerable: Enumerable; + readonly #sorter: Comparer | undefined; + readonly #descending: boolean; + + constructor(enumerable: Enumerable, sorter: Comparer | undefined, descending: boolean) { + super(); + + this.#enumerable = enumerable; + this.#sorter = sorter; + this.#descending = descending; + } + + override nonEnumeratedCount() { + return this.#enumerable.nonEnumeratedCount(); + } + + override maxCount() { + return this.#enumerable.maxCount(); + } + + get comparer() { + return this.#sorter; + } + + thenSelf(comparer?: Comparer): OrderedEnumerable { + return new ThenOrderEnumerable(this, false, comparer); + } + + thenBy(selector: Converter, comparer?: Comparer): OrderedEnumerable { + return new ThenOrderByEnumerable(this, false, selector, comparer); + } + + thenSelfDescending(comparer?: Comparer): OrderedEnumerable { + return new ThenOrderEnumerable(this, true, comparer); + } + + thenByDescending(selector: Converter, comparer?: Comparer): OrderedEnumerable { + return new ThenOrderByEnumerable(this, true, selector, comparer); + } + + override *iterator() { + const arr = new Array(); + + for (const obj of this.#enumerable) { + arr.push(obj); + } + + if (this.#sorter) { + arr.sort(this.#descending ? reverseComparer(this.#sorter) : this.#sorter); + } else { + arr.sort(); + + if (this.#descending) { + arr.reverse(); + } + } + + yield* arr; + } +} + +class EmptyEnumerable extends BaseEnumerable { + static readonly INSTANCE = new EmptyEnumerable(); + + override nonEnumeratedCount() { + return 0; + } + + override maxCount() { + return 0; + } + + override *iterator() { } +} + +class RangeEnumerable extends BaseEnumerable { + readonly #min: number; + readonly #max: number; + readonly #step: number; + + constructor(min: number, max: number, step: number) { + super(); + + this.#min = min; + this.#max = max; + this.#step = step; + } + + get length() { + return Math.ceil((this.#max - this.#min) / this.#step); + } + + override nonEnumeratedCount() { + return this.length; + } + + override maxCount() { + return this.length; + } + + override *iterator() { + for (let i = this.#min; i < this.#max; i += this.#step) { + yield i; + } + } +} + +class BigIntRangeEnumerable extends BaseEnumerable { + readonly #min: bigint; + readonly #max: bigint; + readonly #step: bigint; + + constructor(min: bigint, max: bigint, step: bigint) { + super(); + + this.#min = min; + this.#max = max; + this.#step = step; + } + + get length() { + return Math.ceil(Number((this.#max - this.#min) / this.#step)); + } + + override nonEnumeratedCount() { + return this.length; + } + + override maxCount() { + return this.length; + } + + override *iterator() { + for (let i = this.#min; i < this.#max; i += this.#step) { + yield i; + } + } +} + +class RepeatEnumerable extends BaseEnumerable { + readonly #value: T; + readonly #count: number; + + constructor(value: T, count: number) { + super(); + + this.#value = value; + this.#count = count; + } + + override nonEnumeratedCount() { + return this.#count; + } + + override maxCount() { + return this.#count; + } + + override *iterator() { + let i = this.#count; + + while (i-- > 0) { + yield this.#value; + } + } +} + +class RepeatForeverEnumerable extends BaseEnumerable { + readonly #value: T; + + constructor(value: T) { + super(); + + this.#value = value; + } + + override nonEnumeratedCount() { + return Infinity; + } + + override maxCount() { + return Infinity; + } + + override *iterator() { + while (true) { + yield this.#value; + } + } +} + +class WrappedObject extends BaseEnumerable { + readonly #obj: T; + + constructor(obj: T) { + super(); + + this.#obj = obj; + } + + override nonEnumeratedCount() { + return 1; + } + + override maxCount() { + return 1; + } + + override *iterator() { + yield this.#obj; + } +} + +class WrappedIterable extends BaseEnumerable { + readonly #iterable: Iterable; + + constructor(iterable: Iterable) { + super(); + + this.#iterable = iterable; + } + + override iterator() { + return this.#iterable[Symbol.iterator](); + } +} + +class WrappedArray extends BaseEnumerable { + readonly #array: T[]; + + constructor(array: T[]) { + super(); + + this.#array = array; + } + + override nonEnumeratedCount() { + return this.#array.length; + } + + override maxCount() { + return this.#array.length; + } + + override contains(obj: T, equater?: Equater): boolean { + if (equater) { + return this.#array.some(x => equater(x, obj)); + } + + return this.#array.includes(obj); + } + + override all(predicate: Predicate) { + return this.#array.every(predicate); + } + + override any(predicate?: Predicate) { + if (predicate) { + return this.#array.some(predicate); + } + + return this.#array.length > 0; + } + + override none(predicate?: Predicate) { + if (predicate) { + return !this.#array.some(predicate); + } + + return this.#array.length === 0; + } + + override asArray() { + return this.#array; + } + + override toArray() { + return this.#array.slice(); + } + + override *iterator() { + const length = this.#array.length; + + for (let i = 0; i < length; i++) { + if (i in this.#array) { + yield this.#array[i]; + } + } + } +} + +class WrappedArrayLike extends BaseEnumerable { + readonly #arrayLike: ArrayLike; + + constructor(arrayLike: ArrayLike) { + super(); + + this.#arrayLike = arrayLike; + } + + get arrayLike() { + return this.#arrayLike; + } + + override nonEnumeratedCount() { + return this.#arrayLike.length; + } + + override maxCount() { + return this.#arrayLike.length; + } + + override toArray() { + return Array.from(this.#arrayLike); + } + + override *iterator() { + const length = this.#arrayLike.length; + + for (let i = 0; i < length; i++) { + if (i in this.#arrayLike) { + yield this.#arrayLike[i]; + } + } + } +} + +class WrappedSet extends BaseEnumerable { + readonly #set: Set; + + constructor(set: Set) { + super(); + + this.#set = set; + } + + override nonEnumeratedCount() { + return this.#set.size; + } + + override maxCount() { + return this.#set.size; + } + + override contains(obj: T, equater?: Equater) { + if (equater) { + return super.contains(obj, equater); + } + + return this.#set.has(obj); + } + + override iterator() { + return this.#set.values(); + } +} + +class WrappedMap extends BaseEnumerable<[K, V]> { + readonly #map: Map; + + constructor(map: Map) { + super(); + + this.#map = map; + } + + override nonEnumeratedCount() { + return this.#map.size; + } + + override maxCount() { + return this.#map.size; + } + + override contains(obj: [K, V], equater?: Equater<[K, V]>) { + if (equater) { + return super.contains(obj, equater); + } + + if (!this.#map.has(obj[0])) { + return false; + } + + return this.#map.get(obj[0]) === obj[1]; + } + + override iterator() { + return this.#map.entries(); + } +} + +class GeneratorEnumerable extends BaseEnumerable { + readonly #generator: () => Iterable; + + constructor(generator: () => Iterable) { + super(); + + this.#generator = generator; + } + + override iterator() { + return this.#generator()[Symbol.iterator](); + } +} + +class FunctionEnumerable extends BaseEnumerable { + readonly #f: () => T; + + constructor(f: () => T) { + super(); + + this.#f = f; + } + + override nonEnumeratedCount() { + return Infinity; + } + + override maxCount() { + return Infinity; + } + + override *iterator() { + while (true) { + yield this.#f(); + } + } +} + +class ConcatEnumerable extends BaseEnumerable { + readonly #enumerables: Iterable>; + + constructor(enumerables: Iterable>) { + super(); + + this.#enumerables = enumerables; + } + + override nonEnumeratedCount() { + let n = 0; + + for (const enumerable of this.#enumerables) { + const m = enumerable.nonEnumeratedCount(); + + if (m < 0) { + return super.nonEnumeratedCount(); + } + + n += m; + } + + return n; + } + + override count() { + let n = 0; + + for (const enumerable of this.#enumerables) { + n += enumerable.count(); + } + + return n; + } + + override maxCount() { + let n = 0; + + for (const enumerable of this.#enumerables) { + n += enumerable.maxCount(); + } + + return n; + } + + override *iterator() { + for (const enumerable of this.#enumerables) { + yield* enumerable; + } + } +} + +class DistinctEnumerable extends BaseEnumerable { + readonly #enumerable: Enumerable; + readonly #equater: Equater | undefined; + + constructor(enumerable: Enumerable, equater?: Equater) { + super(); + + this.#enumerable = enumerable; + this.#equater = equater; + } + + override maxCount() { + return this.#enumerable.maxCount(); + } + + override *iterator() { + const set = createEqualitySet(this.#equater); + + for (const obj of this.#enumerable) { + if (set.add(obj)) { + yield obj; + } + } + } +} + +class DistinctByEnumerable extends BaseEnumerable { + readonly #enumerable: Enumerable; + readonly #selector: Converter; + readonly #equater: Equater | undefined; + + constructor(enumerable: Enumerable, selector: Converter, equater?: Equater) { + super(); + + this.#enumerable = enumerable; + this.#selector = selector; + this.#equater = equater; + } + + override maxCount() { + return this.#enumerable.maxCount(); + } + + override *iterator() { + const set = createEqualitySet(this.#equater); + + for (const obj of this.#enumerable) { + if (set.add(this.#selector(obj))) { + yield obj; + } + } + } +} + +class WhereEnumerable extends BaseEnumerable { + readonly #enumerable: Enumerable; + readonly #predicate: FilterPredicate; + + constructor(enumerable: Enumerable, predicate: FilterPredicate) { + super(); + + this.#enumerable = enumerable; + this.#predicate = predicate; + } + + override maxCount() { + return this.#enumerable.maxCount(); + } + + override *iterator() { + for (const obj of this.#enumerable) { + if (this.#predicate(obj)) { + yield obj; + } + } + } +} + +class SelectManyEnumerable extends BaseEnumerable { + readonly #enumerable: Enumerable; + readonly #converter: Converter>; + + constructor(enumerable: Enumerable, converter: Converter>) { + super(); + + this.#enumerable = enumerable; + this.#converter = converter; + } + + override *iterator() { + for (const obj of this.#enumerable) { + yield* this.#converter(obj); + } + } +} + +class IndexedEnumerable extends BaseEnumerable<[number, T]> { + readonly #enumerable: Enumerable; + + constructor(enumerable: Enumerable) { + super(); + + this.#enumerable = enumerable; + } + + override nonEnumeratedCount() { + return this.#enumerable.nonEnumeratedCount(); + } + + override maxCount() { + return this.#enumerable.maxCount(); + } + + override *iterator() { + let i = 0; + + for (const obj of this.#enumerable) { + yield [i++, obj] as [number, T]; + } + } +} + +class SelectEnumerable extends BaseEnumerable { + readonly #enumerable: Enumerable; + readonly #converter: Converter; + + constructor(enumerable: Enumerable, converter: Converter) { + super(); + + this.#enumerable = enumerable; + this.#converter = converter; + } + + override nonEnumeratedCount() { + return this.#enumerable.nonEnumeratedCount(); + } + + override maxCount() { + return this.#enumerable.maxCount(); + } + + override *iterator() { + for (const obj of this.#enumerable) { + yield this.#converter(obj); + } + } +} + +class SkipWhileEnumerable extends BaseEnumerable { + readonly #enumerable: Enumerable; + readonly #predicate: Predicate; + + constructor(enumerable: Enumerable, predicate: Predicate) { + super(); + + this.#enumerable = enumerable; + this.#predicate = predicate; + } + + override maxCount() { + return this.#enumerable.maxCount(); + } + + override *iterator() { + const e = wrapAsIterable(this.#enumerable.iterator()); + + for (const obj of e) { + if (!this.#predicate(obj)) { + yield obj; + break; + } + } + + yield* e; + } +} + +class SkipLastEnumerable extends BaseEnumerable { + readonly #enumerable: Enumerable; + readonly #n: number; + + constructor(enumerable: Enumerable, n: number) { + super(); + + this.#enumerable = enumerable; + this.#n = n; + } + + override nonEnumeratedCount() { + const n = this.#enumerable.nonEnumeratedCount(); + return n < 0 ? super.nonEnumeratedCount() : Math.max(0, n - this.#n); + } + + override maxCount() { + return Math.max(0, this.#enumerable.maxCount() - this.#n); + } + + override *iterator() { + const iterator = this.#enumerable.iterator(); + const buffer = new Array(this.#n); // n > 0 + let i = 0; + + do { + const next = iterator.next(); + + if (next.done) { + return; + } + + buffer[i++] = next.value; + } while (i < this.#n); + + i = 0; + + for (const obj of wrapAsIterable(iterator)) { + yield buffer[i]; + buffer[i] = obj; + i = (i + 1) % this.#n; + } + } +} + +class SkipEnumerable extends BaseEnumerable { + readonly #enumerable: Enumerable; + readonly #n: number; + + constructor(enumerable: Enumerable, n: number) { + super(); + + this.#enumerable = enumerable; + this.#n = n; + } + + override nonEnumeratedCount() { + const n = this.#enumerable.nonEnumeratedCount(); + return n < 0 ? super.nonEnumeratedCount() : Math.max(0, n - this.#n); + } + + override maxCount() { + return Math.max(0, this.#enumerable.maxCount() - this.#n); + } + + override *iterator() { + const iterator = this.#enumerable.iterator(); + let i = 0; + + do { + if (iterator.next().done) { + return; + } + + i++; + } while (i < this.#n); + + yield* wrapAsIterable(iterator); + } +} + +class TakeWhileEnumerable extends BaseEnumerable { + readonly #enumerable: Enumerable; + readonly #predicate: Predicate; + + constructor(enumerable: Enumerable, predicate: Predicate) { + super(); + + this.#enumerable = enumerable; + this.#predicate = predicate; + } + + override maxCount() { + return this.#enumerable.maxCount(); + } + + override *iterator() { + for (const obj of this.#enumerable) { + if (!this.#predicate(obj)) { + return; + } + + yield obj; + } + } +} + +class TakeLastEnumerable extends BaseEnumerable { + readonly #enumerable: Enumerable; + readonly #n: number; + + constructor(enumerable: Enumerable, n: number) { + super(); + + this.#enumerable = enumerable; + this.#n = n; + } + + override nonEnumeratedCount() { + const n = this.#enumerable.nonEnumeratedCount(); + return n < 0 ? super.nonEnumeratedCount() : Math.min(this.#n, n); + } + + override maxCount() { + return Math.min(this.#n, this.#enumerable.maxCount()); + } + + override *iterator() { + const queue = createQueue(this.#n); + + for (const obj of this.#enumerable) { + queue.enqueue(obj); + } + + yield* queue; + } +} + +class TakeEnumerable extends BaseEnumerable { + readonly #enumerable: Enumerable; + readonly #n: number; + + constructor(enumerable: Enumerable, n: number) { + super(); + + this.#enumerable = enumerable; + this.#n = n; + } + + override nonEnumeratedCount() { + const n = this.#enumerable.nonEnumeratedCount(); + return n < 0 ? super.nonEnumeratedCount() : Math.min(this.#n, n); + } + + override maxCount() { + return Math.min(this.#n, this.#enumerable.maxCount()); + } + + override *iterator() { + const iterator = this.#enumerable.iterator(); + let i = this.#n; + + while (i > 0) { + const next = iterator.next(); + + if (next.done) { + return; + } + + yield next.value; + i--; + } + } +} + +class OrderEnumerable extends BaseOrderedEnumerable { + constructor(enumerable: Enumerable, descending: boolean, sorter?: Comparer) { + super(enumerable, sorter, descending); + } +} + +class OrderByEnumerable extends BaseOrderedEnumerable { + constructor(enumerable: Enumerable, descending: boolean, selector: Converter, sorter?: Comparer) { + super(enumerable, OrderByEnumerable.#createSorter(selector, sorter), descending); + } + + static #createSorter(selector: Converter, sorter?: Comparer) { + const _sorter = sorter ?? defaultArrayComparer; + return (a: T, b: T) => _sorter(selector(a), selector(b)); + } +} + +class ThenOrderEnumerable extends BaseOrderedEnumerable { + constructor(enumerable: OrderedEnumerable, descending: boolean, sorter?: Comparer) { + super(enumerable, combineComparers(enumerable.comparer ?? defaultArrayComparer, sorter ?? defaultArrayComparer), descending); + } +} + +class ThenOrderByEnumerable extends BaseOrderedEnumerable { + constructor(enumerable: OrderedEnumerable, descending: boolean, selector: Converter, sorter?: Comparer) { + super(enumerable, ThenOrderByEnumerable.#createCombinedSorter(enumerable.comparer, selector, sorter), descending); + } + + static #createCombinedSorter(baseSorter: Comparer | undefined, selector: Converter, sorter?: Comparer) { + const _baseSorter = baseSorter ?? defaultArrayComparer; + const _sorter = sorter ?? defaultArrayComparer; + return combineComparers(_baseSorter, (a: T, b: T) => _sorter(selector(a), selector(b))); + } +} + +class AppendEnumerable extends BaseEnumerable { + readonly #enumerable: Enumerable; + readonly #obj: T; + + constructor(enumerable: Enumerable, obj: T) { + super(); + + this.#enumerable = enumerable; + this.#obj = obj; + } + + override nonEnumeratedCount() { + const n = this.#enumerable.nonEnumeratedCount(); + return n < 0 ? super.nonEnumeratedCount() : n + 1; + } + + override maxCount() { + return this.#enumerable.maxCount() + 1; + } + + override *iterator() { + yield* this.#enumerable; + yield this.#obj; + } +} + +class PrependEnumerable extends BaseEnumerable { + readonly #enumerable: Enumerable; + readonly #obj: T; + + constructor(enumerable: Enumerable, obj: T) { + super(); + + this.#enumerable = enumerable; + this.#obj = obj; + } + + override nonEnumeratedCount() { + const n = this.#enumerable.nonEnumeratedCount(); + return n < 0 ? super.nonEnumeratedCount() : n + 1; + } + + override maxCount() { + return this.#enumerable.maxCount() + 1; + } + + override *iterator() { + yield this.#obj; + yield* this.#enumerable; + } +} + +class PeekEnumerable extends DelegatedEnumerable { + readonly #action: Action; + + constructor(enumerable: Enumerable, action: Action) { + super(enumerable); + + this.#action = action; + } + + override *iterator() { + for (const obj of wrapAsIterable(super.iterator())) { + this.#action(obj); + yield obj; + } + } +} + +class ZippedEnumerable extends BaseEnumerable<[T, U]> { + readonly #first: Enumerable; + readonly #second: Enumerable; + + constructor(first: Enumerable, second: Enumerable) { + super(); + + this.#first = first; + this.#second = second; + } + + override nonEnumeratedCount() { + const first = this.#first.nonEnumeratedCount(); + const second = this.#second.nonEnumeratedCount(); + + return first < 0 || second < 0 ? super.nonEnumeratedCount() : Math.min(first, second); + } + + override maxCount() { + return Math.min(this.#first.maxCount(), this.#second.maxCount()); + } + + override *iterator() { + const firstIterator = this.#first.iterator(); + const secondIterator = this.#second.iterator(); + + while (true) { + const firstNext = firstIterator.next(); + const secondNext = secondIterator.next(); + + if (firstNext.done || secondNext.done) { + return; + } + + yield [firstNext.value, secondNext.value] as [T, U]; + } + } +} + +class UnionEnumerable extends BaseEnumerable { + readonly #first: Enumerable; + readonly #second: Enumerable; + readonly #equater: Equater | undefined; + + constructor(first: Enumerable, second: Enumerable, equater?: Equater) { + super(); + + this.#first = first; + this.#second = second; + this.#equater = equater; + } + + override maxCount() { + return this.#first.maxCount() + this.#second.maxCount(); + } + + *#iterator() { + yield* this.#first; + yield* this.#second; + } + + override *iterator() { + const set = createEqualitySet(this.#equater); + + for (const obj of this.#iterator()) { + if (set.add(obj)) { + yield obj; + } + } + } +} + +class UnionByEnumerable extends BaseEnumerable { + readonly #first: Enumerable; + readonly #second: Enumerable; + readonly #selector: Converter; + readonly #equater: Equater | undefined; + + constructor(first: Enumerable, second: Enumerable, selector: Converter, equater?: Equater) { + super(); + + this.#first = first; + this.#second = second; + this.#selector = selector; + this.#equater = equater; + } + + override maxCount() { + return this.#first.maxCount() + this.#second.maxCount(); + } + + *#iterator() { + yield* this.#first; + yield* this.#second; + } + + override *iterator() { + const set = createEqualitySet(this.#equater); + + for (const obj of this.#iterator()) { + if (set.add(this.#selector(obj))) { + yield obj; + } + } + } +} + +class ExceptEnumerable extends BaseEnumerable { + readonly #first: Enumerable; + readonly #second: Enumerable; + readonly #equater: Equater | undefined; + + constructor(first: Enumerable, second: Enumerable, equater?: Equater) { + super(); + + this.#first = first; + this.#second = second; + this.#equater = equater; + } + + override maxCount() { + return this.#first.maxCount(); + } + + override *iterator() { + const set = createEqualitySet(this.#equater); + + for (const obj of this.#second) { + set.add(obj); + } + + for (const obj of this.#first) { + if (set.add(obj)) { + yield obj; + } + } + } +} + +class ExceptByEnumerable extends BaseEnumerable { + readonly #first: Enumerable; + readonly #second: Enumerable; + readonly #selector: Converter; + readonly #equater: Equater | undefined; + + constructor(first: Enumerable, second: Enumerable, selector: Converter, equater?: Equater) { + super(); + + this.#first = first; + this.#second = second; + this.#selector = selector; + this.#equater = equater; + } + + override maxCount() { + return this.#first.maxCount(); + } + + override *iterator() { + const set = createEqualitySet(this.#equater); + + for (const obj of this.#second) { + set.add(this.#selector(obj)); + } + + for (const obj of this.#first) { + if (set.add(this.#selector(obj))) { + yield obj; + } + } + } +} + +class IntersectEnumerable extends BaseEnumerable { + readonly #first: Enumerable; + readonly #second: Enumerable; + readonly #equater: Equater | undefined; + + constructor(first: Enumerable, second: Enumerable, equater?: Equater) { + super(); + + this.#first = first; + this.#second = second; + this.#equater = equater; + } + + override maxCount() { + return Math.min(this.#first.maxCount(), this.#second.maxCount()); + } + + override *iterator() { + const set = createEqualitySet(this.#equater); + + for (const obj of this.#second) { + set.add(obj); + } + + for (const obj of this.#first) { + if (set.remove(obj)) { + yield obj; + } + } + } +} + +class IntersectByEnumerable extends BaseEnumerable { + readonly #first: Enumerable; + readonly #second: Enumerable; + readonly #selector: Converter; + readonly #equater: Equater | undefined; + + constructor(first: Enumerable, second: Enumerable, selector: Converter, equater?: Equater) { + super(); + + this.#first = first; + this.#second = second; + this.#selector = selector; + this.#equater = equater; + } + + override maxCount() { + return Math.min(this.#first.maxCount(), this.#second.maxCount()); + } + + override *iterator() { + const set = createEqualitySet(this.#equater); + + for (const obj of this.#second) { + set.add(this.#selector(obj)); + } + + for (const obj of this.#first) { + if (set.remove(this.#selector(obj))) { + yield obj; + } + } + } +} + +class ReversedEnumerable extends BaseEnumerable { + readonly #enumerable: Enumerable; + + constructor(enumerable: Enumerable) { + super(); + + this.#enumerable = enumerable; + } + + override nonEnumeratedCount() { + return this.#enumerable.nonEnumeratedCount(); + } + + override maxCount() { + return this.#enumerable.maxCount(); + } + + override *iterator() { + const buffer = new Array(); + + for (const obj of this.#enumerable) { + buffer.push(obj); + } + + while (buffer.length > 0) { + yield buffer.pop()!; + } + } +} + +class GroupByEnumerable extends BaseEnumerable> { + readonly #enumerable: Enumerable; + readonly #keySelector: Converter; + readonly #elementSelector: Converter; + readonly #keyComparer: Equater | undefined; + + constructor(enumerable: Enumerable, keySelector: Converter, elementSelector?: Converter, keyComparer?: Equater) { + super(); + + this.#enumerable = enumerable; + this.#keySelector = keySelector; + this.#elementSelector = elementSelector ?? identity as Converter; + this.#keyComparer = keyComparer; + } + + override maxCount() { + return this.#enumerable.maxCount(); + } + + override *iterator() { + const groupings = createEqualityMap(this.#keyComparer); + + for (const obj of this.#enumerable) { + const key = this.#keySelector(obj); + let grouping = groupings.get(key); + + if (!grouping) { + groupings.set(key, grouping = []); + } + + grouping.push(this.#elementSelector(obj)); + } + + for (const entry of groupings) { + yield new GroupedEnumerableImpl(entry[0], array(entry[1])); + } + } +} + +class ChunkedEnumerable extends BaseEnumerable { + readonly #enumerable: Enumerable; + readonly #size: number; + + constructor(enumerable: Enumerable, size: number) { + super(); + + this.#enumerable = enumerable; + this.#size = size; + } + + override nonEnumeratedCount() { + const n = this.#enumerable.nonEnumeratedCount(); + return n < 0 ? super.nonEnumeratedCount() : Math.ceil(n / this.#size); + } + + override maxCount() { + return Math.ceil(this.#enumerable.maxCount() / this.#size); + } + + override *iterator() { + let chunk = new Array(); + + for (const obj of this.#enumerable) { + chunk.push(obj); + + if (chunk.length === this.#size) { + yield chunk; + chunk = new Array(); + } + } + + if (chunk.length > 0) { + yield chunk; + } + } +} + +class JoinEnumerable extends BaseEnumerable { + readonly #first: Enumerable; + readonly #second: Enumerable; + readonly #firstKeySelector: Converter; + readonly #secondKeySelector: Converter; + readonly #resultSelector: BiConverter; + readonly #keyComparer: Equater; + + constructor(first: Enumerable, second: Enumerable, firstKeySelector: Converter, secondKeySelector: Converter, resultSelector?: BiConverter, keyComparer?: Equater) { + super(); + + this.#first = first; + this.#second = second; + this.#firstKeySelector = firstKeySelector; + this.#secondKeySelector = secondKeySelector; + this.#resultSelector = resultSelector ?? JoinEnumerable.#defaultResultSelector as BiConverter; + this.#keyComparer = keyComparer ?? strictEquals; + } + + static #defaultResultSelector(first: TOuter, second: TInner): [TOuter, TInner] { + return [first, second]; + } + + override maxCount() { + return this.#first.maxCount() * this.#second.maxCount(); + } + + override *iterator() { + for (const firstObj of this.#first) { + const firstKey = this.#firstKeySelector(firstObj); + + for (const secondObj of this.#second) { + const secondKey = this.#secondKeySelector(secondObj); + + if (this.#keyComparer(firstKey, secondKey)) { + yield this.#resultSelector(firstObj, secondObj); + } + } + } + } +} + +class GroupJoinEnumerable extends BaseEnumerable { + readonly #first: Enumerable; + readonly #second: Enumerable; + readonly #firstKeySelector: Converter; + readonly #secondKeySelector: Converter; + readonly #resultSelector: BiConverter, TResult>; + readonly #keyComparer: Equater; + + constructor(first: Enumerable, second: Enumerable, firstKeySelector: Converter, secondKeySelector: Converter, resultSelector?: BiConverter, TResult>, keyComparer?: Equater) { + super(); + + this.#first = first; + this.#second = second; + this.#firstKeySelector = firstKeySelector; + this.#secondKeySelector = secondKeySelector; + this.#resultSelector = resultSelector ?? GroupJoinEnumerable.#defaultResultSelector as BiConverter, TResult>; + this.#keyComparer = keyComparer ?? strictEquals; + } + + static #defaultResultSelector(first: TOuter, second: Enumerable) { + return new GroupedEnumerableImpl(first, second); + } + + override nonEnumeratedCount() { + return this.#first.nonEnumeratedCount(); + } + + override maxCount() { + return this.#first.maxCount(); + } + + override *iterator() { + for (const firstObj of this.#first) { + const firstKey = this.#firstKeySelector(firstObj); + const secondObjs = new Array(); + + for (const secondObj of this.#second) { + const secondKey = this.#secondKeySelector(secondObj); + + if (this.#keyComparer(firstKey, secondKey)) { + secondObjs.push(secondObj); + } + } + + yield this.#resultSelector(firstObj, array(secondObjs)); + } + } +} + +class RemoveEnumerable extends BaseEnumerable { + readonly #enumerable: Enumerable; + readonly #obj: T; + readonly #all: boolean; + readonly #equater: Equater; + + constructor(enumerable: Enumerable, obj: T, all?: boolean, equater?: Equater) { + super(); + + this.#enumerable = enumerable; + this.#obj = obj; + this.#all = all ?? false; + this.#equater = equater ?? strictEquals; + } + + override maxCount() { + return this.#enumerable.maxCount(); + } + + override *iterator() { + let gotOne = false; + + for (const obj of this.#enumerable) { + if (this.#equater(this.#obj, obj)) { + if (this.#all) { + continue; + } + + if (!gotOne) { + gotOne = true; + continue; + } + } + + yield obj; + } + } +} + +class CacheEnumerable extends DelegatedEnumerable { + #cached = false; + + constructor(enumerable: Enumerable) { + super(enumerable); + } + + override iterator() { + if (!this.#cached) { + this.enumerable = array(this.enumerable.toArray()); + this.#cached = true; + } + + return super.iterator(); + } +} + +//#endregion diff --git a/src/utils.ts b/src/utils.ts new file mode 100644 index 0000000..b74896c --- /dev/null +++ b/src/utils.ts @@ -0,0 +1,121 @@ +import { AsyncComparer, MaybeAsyncComparer, MaybeAsyncIterator } from "./async.js"; +import { Comparer } from "./sync.js"; + +export type AsyncFunction any> = TFunc extends (...args: infer P) => infer R ? (...args: P) => Promise> : never; +export type MaybePromise = T | Promise; +export type MaybeAsyncFunction any> = TFunc extends (...args: infer P) => infer R ? (...args: P) => MaybePromise : never; + +export function isDefined(obj: T): obj is NonNullable { + return obj !== undefined && obj !== null; +} + +export function isIterable(obj: any): obj is Iterable { + return isDefined(obj) && typeof obj[Symbol.iterator] === "function"; +} + +export function isAsyncIterable(obj: any): obj is AsyncIterable { + return isDefined(obj) && typeof obj[Symbol.asyncIterator] === "function"; +} + +export function identity(obj: T) { + return obj; +} + +export function looseEquals(a: T, b: T) { + return a == b; +} + +export function strictEquals(a: T, b: T) { + return a === b; +} + +export function isEquals(a: T, b: T) { + return Object.is(a, b); +} + +export function numberCompare(a: T, b: T) { + return a - b; +} + +export function operatorCompare(a: any, b: any) { + return a < b ? -1 : a > b ? 1 : 0; +} + +export function defaultArrayComparer(a: T, b: T) { + if (a === undefined) { + if (b === undefined) { + return 0; + } + + return 1; + } + + if (b === undefined) { + return -1; + } + + const aStr = a === null ? "null" : a.toString(); + const bStr = b === null ? "null" : b.toString(); + + return aStr > bStr ? 1 : aStr < bStr ? -1 : 0; +} + +export function combineComparers(first: Comparer, second: Comparer): Comparer { + return (a, b) => first(a, b) || second(a, b); +} + +export function combineAsyncComparers(first: MaybeAsyncComparer, second: MaybeAsyncComparer): AsyncComparer { + return async (a, b) => await first(a, b) || await second(a, b); +} + +export function reverseComparer(comparer: Comparer): Comparer { + return (a: T, b: T) => comparer(b, a); +} + +export function reverseAsyncComparer(comparer: MaybeAsyncComparer): AsyncComparer { + return async (a: T, b: T) => await comparer(b, a); +} + +export function* asGenerator(iterator: Iterator) { + while (true) { + const next = iterator.next(); + + if (next.done) { + break; + } + + yield next.value; + } +} + +export async function* asAsyncGenerator(iterator: MaybeAsyncIterator) { + while (true) { + const next = await iterator.next(); + + if (next.done) { + break; + } + + yield next.value; + } +} + +export function asArray(iterable: Iterable) { + return Array.isArray(iterable) ? iterable : Array.from(iterable); +} + +class WrappedIterator implements Iterable { + readonly #iterator: Iterator; + + constructor(iterator: Iterator) { + this.#iterator = iterator; + } + + [Symbol.iterator]() { + return this.#iterator; + } +} + +export function wrapAsIterable(iterator: Iterator): Iterable { + return new WrappedIterator(iterator); +} diff --git a/ts-loader.js b/ts-loader.js new file mode 100644 index 0000000..566b780 --- /dev/null +++ b/ts-loader.js @@ -0,0 +1,4 @@ +import { register } from "node:module"; +import { pathToFileURL } from "node:url"; + +register("ts-node/esm", pathToFileURL("./")); \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..5f32ff9 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "Node16", + "moduleResolution": "Node16", + "alwaysStrict": true, + "esModuleInterop": true, + "noFallthroughCasesInSwitch": true, + "noImplicitAny": true, + "noImplicitOverride": true, + "noErrorTruncation": true, + "noImplicitReturns": true, + "noImplicitThis": true, + "allowUnreachableCode": true, + "strict": true + }, + "exclude": [ + "node_modules" + ], + "ts-node": { + "esm": true, + "experimentalSpecifierResolution": "node" + } +} \ No newline at end of file