1
0

initial commit

This commit is contained in:
2024-05-04 01:19:44 +02:00
commit a172e6a50f
16 changed files with 6514 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/node_modules

213
package-lock.json generated Normal file
View File

@@ -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"
}
}
}
}

18
package.json Normal file
View File

@@ -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"
}
}

2093
src/async.ts Normal file

File diff suppressed because it is too large Load Diff

254
src/bitarray.ts Normal file
View File

@@ -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<boolean>) {
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<boolean> {
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<boolean>) {
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());
}
}

250
src/collector.ts Normal file
View File

@@ -0,0 +1,250 @@
import { Converter } from "./sync.js";
export interface Collector<TElement, TAccumulator, TResult> {
initialize(): TAccumulator;
accumulate(accumulator: TAccumulator, element: TElement): void;
finalize(accumulator: TAccumulator): TResult;
}
// export interface Collector2<TElement, TResult> {
// accumulate(element: TElement): void;
// finalize(): TResult;
// }
class SimpleCollector<TElement, TAccumulator, TResult> implements Collector<TElement, TAccumulator, TResult> {
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<TElement, TAccumulator, TResult>(initialize: () => TAccumulator, accumulate: (accumulator: TAccumulator, element: TElement) => void,
finalize: (accumulator: TAccumulator) => TResult): Collector<TElement, TAccumulator, TResult> {
return new SimpleCollector(initialize, accumulate, finalize);
}
class ToArrayCollector<TElement> implements Collector<TElement, TElement[], TElement[]> {
initialize(): TElement[] {
return [];
}
accumulate(accumulator: TElement[], element: TElement) {
accumulator.push(element);
}
finalize(accumulator: TElement[]) {
return accumulator;
}
}
const toArrayCollector = new ToArrayCollector<any>();
export function toArray<TElement>(): Collector<TElement, any, TElement[]> {
return toArrayCollector;
}
class ToObjectCollector<TElement, TKey extends PropertyKey, TValue> implements Collector<TElement, Record<TKey, TValue>, Record<TKey, TValue>> {
readonly #keySelector: Converter<TElement, TKey>;
readonly #valueSelector: Converter<TElement, TValue>;
constructor(keySelector: Converter<TElement, TKey>, valueSelector: Converter<TElement, TValue>) {
this.#keySelector = keySelector;
this.#valueSelector = valueSelector;
}
initialize() {
return {} as Record<TKey, TValue>;
}
accumulate(accumulator: Record<TKey, TValue>, element: TElement) {
const key = this.#keySelector(element);
const value = this.#valueSelector(element);
accumulator[key] = value;
}
finalize(accumulator: Record<TKey, TValue>) {
return accumulator;
}
}
export function toObject<TElement, TKey extends PropertyKey, TValue>(keySelector: Converter<TElement, TKey>, valueSelector: Converter<TElement, TValue>): Collector<TElement, any, Record<TKey, TValue>> {
return new ToObjectCollector<TElement, TKey, TValue>(keySelector, valueSelector);
}
class ToMapCollector<TElement, TKey, TValue> implements Collector<TElement, Map<TKey, TValue>, Map<TKey, TValue>> {
readonly #keySelector: Converter<TElement, TKey>;
readonly #valueSelector: Converter<TElement, TValue>;
constructor(keySelector: Converter<TElement, TKey>, valueSelector: Converter<TElement, TValue>) {
this.#keySelector = keySelector;
this.#valueSelector = valueSelector;
}
initialize() {
return new Map<TKey, TValue>();
}
accumulate(accumulator: Map<TKey, TValue>, element: TElement) {
const key = this.#keySelector(element);
const value = this.#valueSelector(element);
accumulator.set(key, value);
}
finalize(accumulator: Map<TKey, TValue>) {
return accumulator;
}
}
export function toMap<TElement, TKey, TValue>(keySelector: Converter<TElement, TKey>, valueSelector: Converter<TElement, TValue>): Collector<TElement, any, Map<TKey, TValue>> {
return new ToMapCollector<TElement, TKey, TValue>(keySelector, valueSelector);
}
class ToSetCollector<TElement> implements Collector<TElement, Set<TElement>, Set<TElement>> {
initialize() {
return new Set<TElement>();
}
accumulate(accumulator: Set<TElement>, element: TElement) {
accumulator.add(element);
}
finalize(accumulator: Set<TElement>) {
return accumulator;
}
}
const toSetCollector = new ToSetCollector<any>();
export function toSet<TElement>(): Collector<TElement, any, Set<TElement>> {
return toSetCollector;
}
class JoinCollector implements Collector<any, any[], string> {
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<any, any, string> {
return new JoinCollector(delimiter, prefix, suffix);
}
class SumCollector implements Collector<number, { sum: number }, number> {
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<number, any, number> {
return sumCollector;
}
class BigIntSumCollector implements Collector<bigint, { sum: bigint }, bigint> {
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<bigint, any, bigint> {
return bigintSumCollector;
}
class AverageCollector implements Collector<number, { count: number, sum: number }, number> {
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<number, any, number> {
return averageCollector;
}
class BigIntAverageCollector implements Collector<bigint, { count: number, sum: bigint }, bigint> {
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<bigint, any, bigint> {
return bigintAverageCollector;
}

219
src/equality-map.ts Normal file
View File

@@ -0,0 +1,219 @@
import { Equater } from "./sync.js";
import { MaybeAsyncEquater } from "./async.js";
import { asAsyncGenerator } from "./utils.js";
export interface EqualityMap<K, V> 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<K, V> implements EqualityMap<K, V> {
readonly #map = new Map<K, V>();
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<K, V> implements EqualityMap<K, V> {
readonly #keyComparer: Equater<K>;
readonly #list = new Array<[K, V]>();
constructor(keyComparer: Equater<K>) {
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<K, V>(keyComparer?: Equater<K>): EqualityMap<K, V> {
return keyComparer ? new CustomEqualityMap<K, V>(keyComparer) : new NativeEqualityMap<K, V>();
}
export interface AsyncEqualityMap<K, V> extends AsyncIterable<[K, V]> {
get(key: K): Promise<V | undefined>;
set(key: K, value: V): Promise<V | undefined>;
contains(key: K): Promise<boolean>;
remove(key: K): Promise<V | undefined>;
clear(): Promise<void>;
}
class NativeAsyncEqualityMap<K, V> implements AsyncEqualityMap<K, V> {
readonly #map = new Map<K, V>();
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<K, V> implements AsyncEqualityMap<K, V> {
readonly #keyComparer: MaybeAsyncEquater<K>;
readonly #list = new Array<[K, V]>();
constructor(keyComparer: MaybeAsyncEquater<K>) {
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<K, V>(keyComparer?: MaybeAsyncEquater<K>): AsyncEqualityMap<K, V> {
return keyComparer ? new CustomAsyncEqualityMap<K, V>(keyComparer) : new NativeAsyncEqualityMap<K, V>();
}

195
src/equality-set.ts Normal file
View File

@@ -0,0 +1,195 @@
import { Equater } from "./sync.js";
import { MaybeAsyncEquater } from "./async.js";
import { asAsyncGenerator } from "./utils.js";
export interface EqualitySet<T> extends Iterable<T> {
readonly size: number;
add(obj: T): boolean;
contains(obj: T): boolean;
remove(obj: T): boolean;
clear(): void;
}
class NativeEqualitySet<T> implements EqualitySet<T> {
readonly #set = new Set<T>();
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<T> implements EqualitySet<T> {
readonly #equater: Equater<T>;
readonly #list: T[] = [];
constructor(equater: Equater<T>) {
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<T>(equater?: Equater<T>): EqualitySet<T> {
return equater ? new CustomEqualitySet(equater) : new NativeEqualitySet<T>();
}
export interface AsyncEqualitySet<T> extends AsyncIterable<T> {
readonly size: number;
add(obj: T): Promise<boolean>;
contains(obj: T): Promise<boolean>;
remove(obj: T): Promise<boolean>;
clear(): Promise<void>;
}
class NativeAsyncEqualitySet<T> implements AsyncEqualitySet<T> {
readonly #set = new Set<T>();
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<T> implements AsyncEqualitySet<T> {
readonly #equater: MaybeAsyncEquater<T>;
readonly #list: T[] = [];
constructor(equater: MaybeAsyncEquater<T>) {
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<T>(equater?: MaybeAsyncEquater<T>): AsyncEqualitySet<T> {
return equater ? new CustomAsyncEqualitySet(equater) : new NativeAsyncEqualitySet<T>();
}

4
src/index.ts Normal file
View File

@@ -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";

204
src/queue.ts Normal file
View File

@@ -0,0 +1,204 @@
export interface Queue<T> extends Iterable<T> {
get capacity(): number;
get length(): number;
get free(): number;
isEmpty(): boolean;
isFull(): boolean;
enqueue(obj: T): void;
dequeue(): T | undefined;
clear(): void;
}
class EmptyQueue<T> implements Queue<T> {
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<T> implements Queue<T> {
readonly #capacity: number;
readonly #buffer: T[];
#addIndex = 0;
#getIndex = 0;
constructor(capacity: number) {
this.#capacity = capacity;
this.#buffer = new Array<T>(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<T> implements Queue<T> {
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<any>();
export function createQueue<T>(capacity?: number): Queue<T> {
if (capacity === undefined) {
return new UnboundedArrayQueue<T>();
}
if (capacity < 0) {
throw new Error("Capacity must be greater than or equal to zero.");
}
if (capacity === 0) {
return emptyQueue;
}
return new BoundedArrayQueue<T>(capacity);
}

137
src/random.ts Normal file
View File

@@ -0,0 +1,137 @@
import { BitArray } from "./bitarray.js";
import { asArray } from "./utils.js";
export interface ElementPredicate<T = any> {
(index: number, obj: T): boolean;
}
export interface ElementWeight<T = any> {
(index: number, obj: T): number;
}
export interface RandomGenerator {
(): number;
}
export interface RandomOptions<T> {
predicate?: ElementPredicate<T>;
weight?: ElementWeight<T>;
random?: RandomGenerator;
};
export const alwaysTrue: ElementPredicate = () => true;
export const weightOfOne: ElementWeight = () => 1.0;
export const mathRandom: RandomGenerator = () => Math.random();
const defaultOptions = Object.freeze<Required<RandomOptions<any>>>({
predicate: alwaysTrue,
weight: weightOfOne,
random: mathRandom
});
function mergeOptions<T>(first: RandomOptions<T> | undefined, second: RandomOptions<T> | undefined): RandomOptions<T> | 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<T>(options: RandomOptions<T> | undefined): Required<RandomOptions<T>> {
if (!options) {
return { ...defaultOptions };
}
return {
predicate: options.predicate ?? defaultOptions.predicate,
weight: options.weight ?? defaultOptions.weight,
random: options.random ?? defaultOptions.random
};
}
export function getRandomElement<T>(sequence: Iterable<T>, options?: RandomOptions<T>) {
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<T> {
readonly #elements: Iterable<T>;
readonly #flags: BitArray;
readonly #options: RandomOptions<T>;
public constructor(elements: T[], options?: RandomOptions<T>) {
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<T>(iterable: Iterable<T>, options?: RandomOptions<T>) {
return new this<T>(asArray(iterable), options);
}
public static of<T>(options?: RandomOptions<T>, ...values: T[]) {
return new this<T>(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;
}
}

88
src/sorting.ts Normal file
View File

@@ -0,0 +1,88 @@
import { MaybeAsyncComparer } from "./async.js";
import { reverseAsyncComparer } from "./utils.js";
export interface AsyncSorter {
sort<T>(array: T[], descending: boolean, comparer: MaybeAsyncComparer<T>): Promise<void>;
}
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<T>(array: T[], descending: boolean, comparer: MaybeAsyncComparer<T>) {
await this._sort(array, descending ? reverseAsyncComparer(comparer) : comparer);
}
protected abstract _sort<T>(array: T[], comparer: MaybeAsyncComparer<T>): Promise<void>;
}
class InsertionSorter extends BaseAsyncSorter {
protected override async _sort<T>(array: T[], comparer: MaybeAsyncComparer<T>) {
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<T>(array: T[], comparer: MaybeAsyncComparer<T>) {
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<T>(array: T[], comparer: MaybeAsyncComparer<T>) {
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();

2689
src/sync.ts Normal file

File diff suppressed because it is too large Load Diff

121
src/utils.ts Normal file
View File

@@ -0,0 +1,121 @@
import { AsyncComparer, MaybeAsyncComparer, MaybeAsyncIterator } from "./async.js";
import { Comparer } from "./sync.js";
export type AsyncFunction<TFunc extends (...args: any) => any> = TFunc extends (...args: infer P) => infer R ? (...args: P) => Promise<Awaited<R>> : never;
export type MaybePromise<T> = T | Promise<T>;
export type MaybeAsyncFunction<TFunc extends (...args: any) => any> = TFunc extends (...args: infer P) => infer R ? (...args: P) => MaybePromise<R> : never;
export function isDefined<T = any>(obj: T): obj is NonNullable<T> {
return obj !== undefined && obj !== null;
}
export function isIterable<T = any>(obj: any): obj is Iterable<T> {
return isDefined(obj) && typeof obj[Symbol.iterator] === "function";
}
export function isAsyncIterable<T = any>(obj: any): obj is AsyncIterable<T> {
return isDefined(obj) && typeof obj[Symbol.asyncIterator] === "function";
}
export function identity<T>(obj: T) {
return obj;
}
export function looseEquals<T>(a: T, b: T) {
return a == b;
}
export function strictEquals<T>(a: T, b: T) {
return a === b;
}
export function isEquals<T>(a: T, b: T) {
return Object.is(a, b);
}
export function numberCompare<T extends number | bigint>(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<T>(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<T>(first: Comparer<T>, second: Comparer<T>): Comparer<T> {
return (a, b) => first(a, b) || second(a, b);
}
export function combineAsyncComparers<T>(first: MaybeAsyncComparer<T>, second: MaybeAsyncComparer<T>): AsyncComparer<T> {
return async (a, b) => await first(a, b) || await second(a, b);
}
export function reverseComparer<T>(comparer: Comparer<T>): Comparer<T> {
return (a: T, b: T) => comparer(b, a);
}
export function reverseAsyncComparer<T>(comparer: MaybeAsyncComparer<T>): AsyncComparer<T> {
return async (a: T, b: T) => await comparer(b, a);
}
export function* asGenerator<T>(iterator: Iterator<T>) {
while (true) {
const next = iterator.next();
if (next.done) {
break;
}
yield next.value;
}
}
export async function* asAsyncGenerator<T>(iterator: MaybeAsyncIterator<T>) {
while (true) {
const next = await iterator.next();
if (next.done) {
break;
}
yield next.value;
}
}
export function asArray<T>(iterable: Iterable<T>) {
return Array.isArray(iterable) ? <T[]>iterable : Array.from(iterable);
}
class WrappedIterator<T> implements Iterable<T> {
readonly #iterator: Iterator<T>;
constructor(iterator: Iterator<T>) {
this.#iterator = iterator;
}
[Symbol.iterator]() {
return this.#iterator;
}
}
export function wrapAsIterable<T>(iterator: Iterator<T>): Iterable<T> {
return new WrappedIterator(iterator);
}

4
ts-loader.js Normal file
View File

@@ -0,0 +1,4 @@
import { register } from "node:module";
import { pathToFileURL } from "node:url";
register("ts-node/esm", pathToFileURL("./"));

24
tsconfig.json Normal file
View File

@@ -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"
}
}