Last active
September 3, 2020 15:55
-
-
Save akx/44a848723138f0f67e4ee167d0d03d2c to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
interface InitialGroup<K, T> { | |
key: K; | |
keyString: string; | |
items: T[]; | |
} | |
export interface HierarchicalGroup<K, T, A> extends InitialGroup<K, T> { | |
id: string; | |
level: number; | |
parent?: HierarchicalGroup<K, T, A>; | |
children: AggregatedGroup<K, T, A>[]; | |
} | |
export interface AggregatedGroup<K, T, A> extends HierarchicalGroup<K, T, A> { | |
aggregate: A; | |
} | |
export type Grouper<K, T> = [(t: T) => K, (k: K) => string]; | |
export type Aggregator<K, T, A> = (group: HierarchicalGroup<K, T, A>) => A; | |
const STRING_KEY_SEPARATOR = '\x01'; | |
export function defaultKeyStringifier<T extends Array<any>>(t: T): string { | |
return t.join(STRING_KEY_SEPARATOR); | |
} | |
function buildSimpleGroups<K, T>(input: readonly T[], grouper: Grouper<K, T>): InitialGroup<K, T>[] { | |
const initialGroupMap: Record<string, InitialGroup<K, T>> = {}; | |
const [getKey, getKeyString] = grouper; | |
input.forEach((item) => { | |
const key = getKey(item); | |
const keyString = getKeyString(key); | |
if (!initialGroupMap[keyString]) { | |
initialGroupMap[keyString] = { | |
key, | |
keyString, | |
items: [], | |
}; | |
} | |
initialGroupMap[keyString].items.push(item); | |
}); | |
return Object.values(initialGroupMap); | |
} | |
function buildHierarchy<K, T, A>( | |
initialGroups: InitialGroup<K, T>[], | |
parent: HierarchicalGroup<K, T, A> | undefined, | |
nextGroupers: Grouper<K, T>[], | |
aggregator: Aggregator<K, T, A>, | |
makeChildren: typeof internalAggregate, | |
) { | |
return initialGroups.map((ig, index) => { | |
const hg: HierarchicalGroup<K, T, A> = { | |
...ig, | |
id: [parent ? parent.id : '', `${index}`].filter(Boolean).join(STRING_KEY_SEPARATOR), | |
level: parent ? parent.level + 1 : 0, | |
parent, | |
children: [], | |
}; | |
if (nextGroupers.length) { | |
hg.children = makeChildren(ig.items, nextGroupers, aggregator, hg, makeChildren); | |
} | |
return hg; | |
}); | |
} | |
function aggregateHierarchicalGroups<K, T, A>( | |
hierarchicalGroups: HierarchicalGroup<K, T, A>[], | |
aggregator: Aggregator<K, T, A>, | |
) { | |
return hierarchicalGroups.map((hg) => ({ | |
...hg, | |
aggregate: aggregator(hg), | |
})); | |
} | |
function internalAggregate<K, T, A>( | |
input: readonly T[], | |
groupers: readonly Grouper<K, T>[], | |
aggregator: Aggregator<K, T, A>, | |
parent: HierarchicalGroup<K, T, A> | undefined, | |
makeChildren: typeof internalAggregate, | |
): AggregatedGroup<K, T, A>[] { | |
if (!groupers.length) { | |
return []; | |
} | |
const initialGroups = buildSimpleGroups(input, groupers[0]); | |
const hierarchicalGroups = buildHierarchy(initialGroups, parent, groupers.slice(1), aggregator, makeChildren); | |
return aggregateHierarchicalGroups(hierarchicalGroups, aggregator); | |
} | |
export function aggregate<K, T, A>( | |
input: readonly T[], | |
groupers: readonly Grouper<K, T>[], | |
aggregator: Aggregator<K, T, A>, | |
): AggregatedGroup<K, T, A>[] { | |
return internalAggregate(input, groupers, aggregator, undefined, internalAggregate); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment