1
0
This commit is contained in:
Herve BECHER
2025-05-24 12:11:44 +02:00
parent e5e6bffe1e
commit 0fd0e3bada
24 changed files with 1702 additions and 688 deletions

245
src/comparer/async.ts Normal file
View File

@@ -0,0 +1,245 @@
import { MaybeAsyncConverter } from "../types.js";
import { Nullable } from "../utils.js";
import { AsyncComparer, MaybeAsyncComparisonOrComparer, Comparer, MaybeAsyncComparison, AsyncComparison } from "./types.js";
export function isAsyncComparer<T>(obj: any): obj is AsyncComparer<T> {
return obj instanceof BaseAsyncComparer;
}
export function asAsyncComparer<T>(comparer: MaybeAsyncComparisonOrComparer<T>): AsyncComparer<T> {
return typeof comparer === "function" ? createAsyncComparer(comparer) : isAsyncComparer<T>(comparer) ? comparer : new WrappedAsyncComparer(comparer);
}
export function fromSync<T>(comparer: Comparer<T>): AsyncComparer<T> {
return new WrappedAsyncComparer(comparer);
}
export function createAsyncComparer<T>(comparison: MaybeAsyncComparison<T>): AsyncComparer<T> {
return new SimpleAsyncComparer(comparison);
}
export function createAsyncComparerUsing<T, U>(projection: MaybeAsyncConverter<T, U>, comparison?: MaybeAsyncComparisonOrComparer<U>): AsyncComparer<T> {
return new MappedAsyncComparer(projection, comparison);
}
export function combineNullableAsyncComparers<T>(comparers: Nullable<MaybeAsyncComparisonOrComparer<T>>[]): AsyncComparer<T> | undefined {
let result = defaultAsyncComparer;
for (const comparer of comparers) {
if (!comparer) {
continue;
}
result = result.then(asAsyncComparer(comparer));
}
return result === defaultAsyncComparer ? undefined : result;
}
export abstract class BaseAsyncComparer<T> implements AsyncComparer<T> {
#cachedBoundComparison: AsyncComparison<T> | undefined;
public abstract compare(a: T, b: T): Promise<number>;
public comparison(): AsyncComparison<T> {
return this.#cachedBoundComparison ??= this.compare.bind(this);
}
public reverse(): AsyncComparer<T> {
return new ReversedAsyncComparer(this);
}
public then(comparer: AsyncComparer<T>): AsyncComparer<T> {
return new ThenAsyncComparer(this, comparer);
}
public thenCompare(comparison: MaybeAsyncComparison<T>): AsyncComparer<T> {
return this.then(createAsyncComparer(comparison));
}
public thenCompareUsing<U>(projection: MaybeAsyncConverter<T, U>, comparer?: MaybeAsyncComparisonOrComparer<U>): AsyncComparer<T> {
return this.then(createAsyncComparerUsing(projection, comparer));
}
public nullAwareComparer(): AsyncComparer<Nullable<T>> {
return new NullAwareAsyncComparer(this);
}
}
class WrappedAsyncComparer<T> extends BaseAsyncComparer<T> {
readonly #base: Comparer<T>;
constructor(base: Comparer<T>) {
super();
this.#base = base;
}
public override async compare(a: T, b: T) {
return this.#base.compare(a, b);
}
}
class SimpleAsyncComparer<T> extends BaseAsyncComparer<T> {
readonly #comparison: MaybeAsyncComparison<T>;
public constructor(comparison: MaybeAsyncComparison<T>) {
super();
this.#comparison = comparison;
}
public async compare(a: T, b: T) {
return await this.#comparison(a, b);
}
}
class MappedAsyncComparer<T, U> extends BaseAsyncComparer<T> {
readonly #projection: MaybeAsyncConverter<T, U>;
readonly #comparer: AsyncComparer<U>;
public constructor(projection: MaybeAsyncConverter<T, U>, comparer?: MaybeAsyncComparisonOrComparer<U>) {
super();
this.#projection = projection;
this.#comparer = comparer ? asAsyncComparer(comparer) : defaultAsyncComparer;
}
public async compare(a: T, b: T) {
return await this.#comparer.compare(await this.#projection(a), await this.#projection(b));
}
}
class ReversedAsyncComparer<T> extends BaseAsyncComparer<T> {
readonly #base: AsyncComparer<T>;
public constructor(base: AsyncComparer<T>) {
super();
this.#base = base;
}
public async compare(a: T, b: T) {
return await this.#base.compare(b, a);
}
public override reverse() {
return this.#base;
}
}
class ThenAsyncComparer<T> extends BaseAsyncComparer<T> {
readonly #base: AsyncComparer<T>;
readonly #comparer: AsyncComparer<T>;
public constructor(base: AsyncComparer<T>, comparer: AsyncComparer<T>) {
super();
this.#base = base;
this.#comparer = comparer;
}
public async compare(a: T, b: T) {
return await this.#base.compare(a, b) || await this.#comparer.compare(a, b);
}
}
export const defaultAsyncComparer: AsyncComparer<any> = new class DefaultAsyncComparer extends BaseAsyncComparer<any> {
public override async compare(a: any, b: any): Promise<number> {
if (a === undefined) {
if (b === undefined) {
return 0;
}
return 1;
}
if (b === undefined) {
return -1;
}
const aStr = `${a}`, bStr = `${b}`;
return aStr > bStr ? 1 : aStr < bStr ? -1 : 0;
}
};
export function getDefaultAsyncComparer<T>(): AsyncComparer<T> {
return defaultAsyncComparer;
}
class NullAwareAsyncComparer<T> extends BaseAsyncComparer<Nullable<T>> {
readonly #base: AsyncComparer<T>;
constructor(baseComparer: AsyncComparer<T>) {
super();
this.#base = baseComparer;
}
public override async compare(a: Nullable<T>, b: Nullable<T>): Promise<number> {
if (a === undefined) {
if (b === undefined) {
return 0;
}
return 1;
}
if (b === undefined) {
return -1;
}
if (a === null) {
if (b === null) {
return 0;
}
return 1;
}
if (b === null) {
return -1;
}
return await this.#base.compare(a, b);
}
public override nullAwareComparer(): AsyncComparer<Nullable<T>> {
return this;
}
}
export function nullAwareComparer<T>(baseComparer: AsyncComparer<T>): AsyncComparer<Nullable<T>> {
return new NullAwareAsyncComparer(baseComparer);
}
export const stringAsyncComparer: AsyncComparer<string> = new class StringAsyncComparer extends BaseAsyncComparer<string> {
public override async compare(a: string, b: string): Promise<number> {
return a.localeCompare(b);
}
};
export const numberAsyncComparer: AsyncComparer<number> = new class NumberAsyncComparer extends BaseAsyncComparer<number> {
public override async compare(a: number, b: number): Promise<number> {
return a - b;
}
};
export const bigintAsyncComparer: AsyncComparer<bigint> = new class BigIntAsyncComparer extends BaseAsyncComparer<bigint> {
public override async compare(a: bigint, b: bigint): Promise<number> {
return Number(a - b);
}
};
export const booleanAsyncComparer: AsyncComparer<boolean> = new class BooleanAsyncComparer extends BaseAsyncComparer<boolean> {
public override async compare(a: boolean, b: boolean): Promise<number> {
return Number(a) - Number(b);
}
};
export const dateAsyncComparer: AsyncComparer<Date> = new class DateAsyncComparer extends BaseAsyncComparer<Date> {
public override async compare(a: Date, b: Date): Promise<number> {
return a.getTime() - b.getTime();
}
};

244
src/comparer/sync.ts Normal file
View File

@@ -0,0 +1,244 @@
import { Converter } from "../types.js";
import { Nullable } from "../utils.js";
import { fromSync } from "./async.js";
import { Comparer, ComparisonOrComparer, Comparison, AsyncComparer } from "./types.js";
export function isComparer<T>(obj: any): obj is Comparer<T> {
return obj instanceof BaseComparer;
}
export function asComparer<T>(comparer: ComparisonOrComparer<T>) {
return typeof comparer === "function" ? createComparer(comparer) : comparer;
}
export function asComparison<T>(comparer: ComparisonOrComparer<T>) {
return typeof comparer === "function" ? comparer : comparer.comparison();
}
export function createComparer<T>(comparison: Comparison<T>): Comparer<T> {
return new SimpleComparer(comparison);
}
export function createComparerUsing<T, U>(projection: Converter<T, U>, comparison?: ComparisonOrComparer<U>): Comparer<T> {
return new MappedComparer(projection, comparison);
}
export function reverseComparison<T>(comparison: Comparison<T>): Comparison<T> {
return (a, b) => comparison(b, a);
}
export function combineNullableComparers<T>(comparers: Nullable<ComparisonOrComparer<T>>[]) {
let result = defaultComparer;
for (const comparer of comparers) {
if (!comparer) {
continue;
}
result = result.then(asComparer(comparer));
}
return result === defaultComparer ? undefined : result;
}
export abstract class BaseComparer<T> implements Comparer<T> {
#cachedBoundComparison: Comparison<T> | undefined;
public abstract compare(a: T, b: T): number;
public comparison(): Comparison<T> {
return this.#cachedBoundComparison ??= this.compare.bind(this);
}
public reverse(): Comparer<T> {
return new ReversedComparer(this);
}
public then(comparer: Comparer<T>): Comparer<T> {
return new ThenComparer(this, comparer);
}
public thenCompare(comparison: Comparison<T>): Comparer<T> {
return this.then(createComparer(comparison));
}
public thenCompareUsing<U>(projection: Converter<T, U>, comparison?: ComparisonOrComparer<U>): Comparer<T> {
return this.then(createComparerUsing(projection, comparison));
}
public toAsync(): AsyncComparer<T> {
return fromSync<T>(this);
}
public nullAwareComparer(): Comparer<Nullable<T>> {
return new NullAwareComparer(this);
}
}
class SimpleComparer<T> extends BaseComparer<T> {
readonly #comparison: Comparison<T>;
public constructor(comparison: Comparison<T>) {
super();
this.#comparison = comparison;
}
public override compare(a: T, b: T) {
return this.#comparison(a, b);
}
public override comparison() {
return this.#comparison;
}
}
class MappedComparer<T, U> extends BaseComparer<T> {
readonly #projection: Converter<T, U>;
readonly #comparison: Comparer<U>;
public constructor(projection: Converter<T, U>, comparison?: ComparisonOrComparer<U>) {
super();
this.#projection = projection;
this.#comparison = comparison ? asComparer(comparison) : defaultComparer;
}
public override compare(a: T, b: T) {
return this.#comparison.compare(this.#projection(a), this.#projection(b));
}
}
class ReversedComparer<T> extends BaseComparer<T> {
readonly #base: Comparer<T>;
public constructor(base: Comparer<T>) {
super();
this.#base = base;
}
public override compare(a: T, b: T): number {
return this.#base.compare(b, a);
}
public override reverse(): Comparer<T> {
return this.#base;
}
}
class ThenComparer<T> extends BaseComparer<T> {
readonly #base: Comparer<T>;
readonly #comparer: Comparer<T>;
public constructor(base: Comparer<T>, comparer: Comparer<T>) {
super();
this.#base = base;
this.#comparer = comparer;
}
public override compare(a: T, b: T) {
return this.#base.compare(a, b) || this.#comparer.compare(a, b);
}
}
export const defaultComparer: Comparer<any> = new class DefaultComparer extends BaseComparer<any> {
public override compare(a: any, b: any): number {
if (a === undefined) {
if (b === undefined) {
return 0;
}
return 1;
}
if (b === undefined) {
return -1;
}
const aStr = `${a}`, bStr = `${b}`;
return aStr > bStr ? 1 : aStr < bStr ? -1 : 0;
}
};
export function getDefaultComparer<T>(): Comparer<T> {
return defaultComparer;
}
class NullAwareComparer<T> extends BaseComparer<Nullable<T>> {
readonly #base: Comparer<T>;
constructor(baseComparer: Comparer<T>) {
super();
this.#base = baseComparer;
}
public override compare(a: Nullable<T>, b: Nullable<T>): number {
if (a === undefined) {
if (b === undefined) {
return 0;
}
return 1;
}
if (b === undefined) {
return -1;
}
if (a === null) {
if (b === null) {
return 0;
}
return 1;
}
if (b === null) {
return -1;
}
return this.#base.compare(a, b);
}
public override nullAwareComparer(): Comparer<Nullable<T>> {
return this;
}
}
export function nullAwareComparer<T>(baseComparer: Comparer<T>): Comparer<Nullable<T>> {
return new NullAwareComparer(baseComparer);
}
export const stringComparer: Comparer<string> = new class StringComparer extends BaseComparer<string> {
public override compare(a: string, b: string): number {
return a.localeCompare(b);
}
};
export const numberComparer: Comparer<number> = new class NumberComparer extends BaseComparer<number> {
public override compare(a: number, b: number): number {
return a - b;
}
};
export const bigintComparer: Comparer<bigint> = new class BigIntComparer extends BaseComparer<bigint> {
public override compare(a: bigint, b: bigint): number {
return Number(a - b);
}
};
export const booleanComparer: Comparer<boolean> = new class BooleanComparer extends BaseComparer<boolean> {
public override compare(a: boolean, b: boolean): number {
return Number(a) - Number(b);
}
};
export const dateComparer: Comparer<Date> = new class DateComparer extends BaseComparer<Date> {
public override compare(a: Date, b: Date): number {
return a.getTime() - b.getTime();
}
};

35
src/comparer/types.ts Normal file
View File

@@ -0,0 +1,35 @@
import { AsyncFunction, Converter, MaybeAsyncConverter, MaybeAsyncFunction } from "../types.js";
import { Nullable } from "../utils.js";
export interface Comparer<T> {
compare(a: T, b: T): number;
comparison(): Comparison<T>;
reverse(): Comparer<T>;
then(comparer: Comparer<T>): Comparer<T>;
thenCompare(comparison: Comparison<T>): Comparer<T>;
thenCompareUsing<U>(projection: Converter<T, U>, comparison?: ComparisonOrComparer<U>): Comparer<T>;
toAsync(): AsyncComparer<T>;
nullAwareComparer(): Comparer<Nullable<T>>;
}
export interface AsyncComparer<T> {
compare(a: T, b: T): Promise<number>;
comparison(): AsyncComparison<T>;
reverse(): AsyncComparer<T>;
then(comparer: AsyncComparer<T>): AsyncComparer<T>;
thenCompare(comparison: AsyncComparison<T>): AsyncComparer<T>;
thenCompareUsing<U>(projection: MaybeAsyncConverter<T, U>, comparison?: AsyncComparisonOrComparer<U>): AsyncComparer<T>;
nullAwareComparer(): AsyncComparer<Nullable<T>>;
}
export type Comparison<T> = (first: T, second: T) => number;
export type ComparisonOrComparer<T> = Comparison<T> | Comparer<T>;
export type Equater<T> = (first: T, second: T) => boolean;
export type AsyncComparison<T> = AsyncFunction<Comparison<T>>;
export type MaybeAsyncComparison<T> = MaybeAsyncFunction<Comparison<T>>;
export type AsyncComparisonOrComparer<T> = AsyncComparison<T> | AsyncComparer<T>;
export type MaybeAsyncComparisonOrComparer<T> = MaybeAsyncComparison<T> | Comparer<T> | AsyncComparer<T>;
export type AsyncEquater<T> = AsyncFunction<Equater<T>>;
export type MaybeAsyncEquater<T> = MaybeAsyncFunction<Equater<T>>;