interface SerializedTrie { // terminal node? t: 1 | 0; // value of the node v: T; // optional children c?: { [s: string]: SerializedTrie }; } export class TrieNode { public terminal: boolean; public value: T; public children: Map>; public serialized: SerializedTrie; constructor(ser: SerializedTrie) { this.terminal = ser.t == 1; this.value = ser.v; this.children = new Map(); this.serialized = ser; if (ser.c != null) { for (const [key, value] of Object.entries(ser.c)) { this.children.set(key, new TrieNode(value)); } } } } export default class Trie { public root: TrieNode; constructor(ser: SerializedTrie) { this.root = new TrieNode(ser); } public nodeForPrefix(key: string): { chain: string[]; node: TrieNode | null; } { let chain = []; let node = this.root; let remaining = key; while (node && remaining.length > 0) { let exactChild = null; console.log('remaining: ', remaining); for (const [childKey, child] of node.children.entries()) { if (remaining.startsWith(childKey)) { console.log('exact match for: ', childKey); exactChild = child; chain.push(childKey); remaining = remaining.slice(childKey.length); break; } } // if an exact match was found, continue iterating if (exactChild) { node = exactChild; continue; } console.log('looking for partial match for ', remaining); for (const [childKey, child] of node.children.entries()) { const startsWith = childKey.startsWith(remaining); console.log( 'test ', childKey, ' against ', remaining, ': ', startsWith, ' ', child.serialized, ); if (startsWith) { console.log('partial match for: ', remaining, ': ', child.serialized); chain.push(childKey); return { chain, node: child }; } } console.log('did not find partial, bailing!'); return { chain, node: null }; } // // return remaining.length === 0 && node && node.terminal ? node : null; console.log('returning child ', node, ' for remaining ', remaining); return { chain, node }; } }