import { updateMap } from "../updateMap"; type SplitterNode = { keys: Set; payload: Payload; }; type SplitterOptions = { createPayload: (keys: ReadonlySet) => Payload; splitPayload: ( payload: Payload, keys1: ReadonlySet, keys2: ReadonlySet ) => [yesPayload: Payload, noPayload: Payload]; }; export class Splitter { private readonly nodes = new Set>(); private readonly keyToNode = new Map>(); private readonly createPayload: (keys: ReadonlySet) => Payload; private readonly splitPayload: ( payload: Payload, keys1: ReadonlySet, keys2: ReadonlySet ) => [Payload, Payload]; constructor(options: SplitterOptions) { this.createPayload = options.createPayload; this.splitPayload = options.splitPayload; } public apply(keys: readonly Key[]) { // sort keys by belonging nodes. const newKeys = new Set(); const nodeMap = new Map, Key[]>(); for (const key of keys) { const node = this.keyToNode.get(key); if (node === undefined) { newKeys.add(key); } else { updateMap(nodeMap, node, [], (c) => (c.push(key), c)); } } // keys that has not been recognized yet are treated as a set for new node. if (newKeys.size > 0) { this.add(newKeys, this.createPayload(newKeys)); } // Existing keys may split existing set. for (const [node, keys] of nodeMap) { const [yes, no] = filterBySet(node.keys, new Set(keys)); if (no.length === 0) { // not split continue; } // split the node into two const keys1 = new Set(yes); const keys2 = new Set(no); const [payload1, payload2] = this.splitPayload( node.payload, keys1, keys2 ); this.nodes.delete(node); this.add(keys1, payload1); this.add(keys2, payload2); } } private add(keys: Set, payload: Payload) { const node: SplitterNode = { keys, payload }; this.nodes.add(node); for (const key of keys) { this.keyToNode.set(key, node); } } public entries(): IterableIterator> { return this.nodes.values(); } } /** * Filter given array by whether it belongs to given set. */ function filterBySet(values: Iterable, set: ReadonlySet): [T[], T[]] { const yes = []; const no = []; for (const v of values) { if (set.has(v)) { yes.push(v); } else { no.push(v); } } return [yes, no]; }