UNPKG

@stnekroman/tstools

Version:

Set of handy tools for TypeScript development

212 lines (168 loc) 8.61 kB
import { Arrays } from './Arrays'; import { Functions } from './Functions'; import { Objects } from './Objects'; import { Types } from './Types'; export function GroupBy<T extends {}>(data: T[]) { return { toObject<KEY extends keyof T & PropertyKey, TVAL extends PropertyKey & T[KEY] = T[KEY] & PropertyKey>(field: KEY) : Record<TVAL, T[]> { return new GroupBy.ObjectGroupByBuilder(Functions.extractor<T>(field) as Functions.MapFunction<T, TVAL>).build().consumeAll(data).result as Record<TVAL, T[]>; }, toMap<KEY extends keyof T, TVAL extends T[KEY] = T[KEY]>(field: KEY) : Map<TVAL, T[]>{ return (new GroupBy.MapGroupByBuilder(Functions.extractor<T>(field) as Functions.MapFunction<T, TVAL>).build().consumeAll(data).result) as Map<TVAL, unknown> as Map<TVAL, T[]>; } }; } export namespace GroupBy { export abstract class Accumulator<T, R> { public abstract consume(data : T) : this; public abstract get result() : Types.DeepReadonly<R>; public abstract clear() : void; public consumeAll(datas : T[]) : this { for (const data of datas) { this.consume(data); } return this; } } export function toObject<T extends {}>() { return { byExtractor<KEY extends PropertyKey>(extractor : Functions.MapFunction<T, KEY>) : ObjectGroupByBuilder<T, KEY> { return new ObjectGroupByBuilder(extractor); }, byField<KEY extends keyof T, TVAL extends PropertyKey & T[KEY] = T[KEY] & PropertyKey>(field : KEY) : ObjectGroupByBuilder<T, TVAL> { return new ObjectGroupByBuilder(Functions.extractor<T>(field) as Functions.MapFunction<T, TVAL>); } }; } export function toMap<T extends {}>() { return { byExtractor<KEY>(extractor : Functions.MapFunction<T, KEY>) : MapGroupByBuilder<T, KEY> { return new MapGroupByBuilder(extractor); }, byField<KEY extends keyof T, TVAL extends PropertyKey & T[KEY] = T[KEY] & PropertyKey>(field : KEY) : MapGroupByBuilder<T, TVAL> { return new MapGroupByBuilder(Functions.extractor<T>(field) as Functions.MapFunction<T, TVAL>); } }; } export class ObjectGroupByBuilder<T extends {}, KEY extends PropertyKey> { constructor(private readonly keyExtractor : Functions.MapFunction<T, KEY>) {} public valueExtractor<VALUE>(extractor : Functions.MapFunction<T, VALUE>) : ObjectGroupByBuilderWithValue<T, KEY, VALUE> { return new ObjectGroupByBuilderWithValue(this.keyExtractor, extractor); } public build<GROUP>(groupAccumulatorsFactory : Functions.Provider<Accumulator<T, GROUP>>) : ObjectGroupByAccumulator<T, KEY, T, GROUP>; public build() : ObjectGroupByAccumulator<T, KEY, T, T[]>; build<GROUP = T[]>(groupAccumulatorsFactory ?: Functions.Provider<Accumulator<T, GROUP>>) : ObjectGroupByAccumulator<T, KEY, T, GROUP | T[]> { return new ObjectGroupByBuilderWithValue(this.keyExtractor, i => i).build(groupAccumulatorsFactory!); } } export class MapGroupByBuilder<T extends {}, KEY> { constructor(private readonly keyExtractor : Functions.MapFunction<T, KEY>) {} public valueExtractor<VALUE>(extractor : Functions.MapFunction<T, VALUE>) : MapGroupByBuilderWithValue<T, KEY, VALUE> { return new MapGroupByBuilderWithValue(this.keyExtractor, extractor); } public build<GROUP>(groupAccumulatorsFactory : Functions.Provider<Accumulator<T, GROUP>>) : MapGroupByAccumulator<T, KEY, T, GROUP>; public build() : MapGroupByAccumulator<T, KEY, T, T[]>; build<GROUP = T[]>(groupAccumulatorsFactory ?: Functions.Provider<Accumulator<T, GROUP>>) : MapGroupByAccumulator<T, KEY, T, GROUP | T[]> { return new MapGroupByBuilderWithValue(this.keyExtractor, i => i).build(groupAccumulatorsFactory!); } } class ObjectGroupByBuilderWithValue<T extends {}, KEY extends PropertyKey, VALUE> { constructor( private readonly keyExtractor : Functions.MapFunction<T, KEY>, private readonly valueExtractor : Functions.MapFunction<T, VALUE> ) {} public build<GROUP>(groupAccumulatorsFactory : Functions.Provider<Accumulator<VALUE, GROUP>>) : ObjectGroupByAccumulator<T, KEY, VALUE, GROUP>; public build() : ObjectGroupByAccumulator<T, KEY, VALUE, VALUE[]>; build<GROUP = VALUE[]>(groupAccumulatorsFactory ?: Functions.Provider<Accumulator<VALUE, GROUP | VALUE[]>>) : ObjectGroupByAccumulator<T, KEY, VALUE, GROUP | VALUE[]> { if (!groupAccumulatorsFactory) { groupAccumulatorsFactory = () => new ArrayAccumulator<VALUE>(); } return new ObjectGroupByAccumulator(this.keyExtractor, this.valueExtractor, groupAccumulatorsFactory); } } class MapGroupByBuilderWithValue<T extends {}, KEY, VALUE> { constructor( private readonly keyExtractor : Functions.MapFunction<T, KEY>, private readonly valueExtractor : Functions.MapFunction<T, VALUE> ) {} public build<GROUP>(groupAccumulatorsFactory : Functions.Provider<Accumulator<VALUE, GROUP>>) : MapGroupByAccumulator<T, KEY, VALUE, GROUP>; public build() : MapGroupByAccumulator<T, KEY, VALUE, VALUE[]>; build<GROUP = VALUE[]>(groupAccumulatorsFactory ?: Functions.Provider<Accumulator<VALUE, GROUP | VALUE[]>>) : MapGroupByAccumulator<T, KEY, VALUE, GROUP | VALUE[]> { if (!groupAccumulatorsFactory) { groupAccumulatorsFactory = () => new ArrayAccumulator<VALUE>(); } return new MapGroupByAccumulator(this.keyExtractor, this.valueExtractor, groupAccumulatorsFactory); } } abstract class GroupByAccumulator<T extends {}, KEY, VALUE, GROUP, RESULT_CONTAINER> extends Accumulator<T, RESULT_CONTAINER> { private readonly groupAccumulators : Map<KEY, Accumulator<VALUE, GROUP>> = new Map(); constructor( private readonly keyExtractor : Functions.MapFunction<T, KEY>, private readonly valueExtractor : Functions.MapFunction<T, VALUE>, private readonly groupAccumulatorsFactory : Functions.Provider<Accumulator<VALUE, GROUP>> ) {super();} public override consume(data: T) : this { const key = this.keyExtractor(data); const value : VALUE = this.valueExtractor(data); let groupAccumulator = this.groupAccumulators.get(key); if (groupAccumulator === undefined) { groupAccumulator = this.groupAccumulatorsFactory(); this.groupAccumulators.set(key, groupAccumulator); this.set(key, groupAccumulator.result); // ref to result should be stable, by contract } groupAccumulator.consume(value); return this; } public override clear(): void { this.groupAccumulators.clear(); } public abstract set(key: KEY, group: GROUP | Types.DeepReadonly<GROUP>) : void; // returns object, containing grouped result, no cloning for performance reasons, use returned refs carefully public abstract get result() : Types.DeepReadonly<RESULT_CONTAINER>; } export class ObjectGroupByAccumulator<T extends {}, KEY extends PropertyKey, VALUE, GROUP> extends GroupByAccumulator<T, KEY, VALUE, GROUP, Record<KEY, GROUP>> { readonly #result : Record<KEY, GROUP> = Object.create(null); public override set(key: KEY, group: GROUP) : void { this.#result[key] = group; } public override get result() : Types.DeepReadonly<Record<KEY, GROUP>> { return this.#result as Types.DeepReadonly<Record<KEY, GROUP>>; } public override clear(): void { super.clear(); Objects.forEach(this.#result, (key) => { delete this.#result[key]; }); } } export class MapGroupByAccumulator<T extends {}, KEY, VALUE, GROUP> extends GroupByAccumulator<T, KEY, VALUE, GROUP, Map<KEY, GROUP>> { readonly #result : Map<KEY, GROUP> = new Map(); public override set(key: KEY, group: GROUP) : void { this.#result.set(key, group); } public override get result() : Types.DeepReadonly<Map<KEY, GROUP>> { return this.#result as Types.DeepReadonly<Map<KEY, GROUP>>; } public override clear(): void { super.clear(); this.#result.clear(); } } export class ArrayAccumulator<T> extends Accumulator<T, T[]> { readonly #result : T[] = []; public override consume(data: T): this { this.#result.push(data); return this; } public override consumeAll(datas: T[]): this { Arrays.pushAll(this.#result, datas); return this; } public override get result() : Types.DeepReadonly<T[]> { return this.#result as Types.DeepReadonly<T[]>; } public override clear(): void { this.#result.length = 0; } } }