initial commit
This commit is contained in:
2093
src/async.ts
Normal file
2093
src/async.ts
Normal file
File diff suppressed because it is too large
Load Diff
254
src/bitarray.ts
Normal file
254
src/bitarray.ts
Normal 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
250
src/collector.ts
Normal 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
219
src/equality-map.ts
Normal 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
195
src/equality-set.ts
Normal 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
4
src/index.ts
Normal 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
204
src/queue.ts
Normal 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
137
src/random.ts
Normal 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
88
src/sorting.ts
Normal 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
2689
src/sync.ts
Normal file
File diff suppressed because it is too large
Load Diff
121
src/utils.ts
Normal file
121
src/utils.ts
Normal 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);
|
||||
}
|
||||
Reference in New Issue
Block a user