@stnekroman/tstools
Version:
Set of handy tools for TypeScript development
212 lines (168 loc) • 8.61 kB
text/typescript
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;
}
}
}