diff --git a/Build/lib/trie.ts b/Build/lib/trie.ts index e553840c..53b6e4e9 100644 --- a/Build/lib/trie.ts +++ b/Build/lib/trie.ts @@ -2,14 +2,22 @@ * Suffix Trie based on Mnemonist Trie */ -export const SENTINEL: string = String.fromCodePoint(0); +export const SENTINEL = '\u0000'; + +type TrieNode = { + [SENTINEL]?: true +} & { + [key: string & {}]: TrieNode | undefined +}; + +// type TrieNode = Map; /** * @param {string[] | Set} [from] */ export const createTrie = (from?: string[] | Set) => { let size = 0; - const root: any = {}; + const root: TrieNode = {}; /** * Method used to add the given prefix to the trie. @@ -17,12 +25,12 @@ export const createTrie = (from?: string[] | Set) => { * @param {string} suffix - Prefix to follow. */ const add = (suffix: string): void => { - let node: any = root; + let node: TrieNode = root; let token: string; for (let i = suffix.length - 1; i >= 0; i--) { token = suffix[i]; node[token] ||= {}; - node = node[token]; + node = node[token]!; } // Do we need to increase size? @@ -36,15 +44,17 @@ export const createTrie = (from?: string[] | Set) => { * @param {string} suffix */ const contains = (suffix: string): boolean => { - let node: any = root; + let node: TrieNode = root; let token: string; for (let i = suffix.length - 1; i >= 0; i--) { token = suffix[i]; - node = node[token]; + const n = node[token]; + if (n === undefined) return false; + // if (n === true) return false; - if (node == null) return false; + node = n; } return true; @@ -57,20 +67,23 @@ export const createTrie = (from?: string[] | Set) => { * @return {string[]} */ const find = (suffix: string, includeEqualWithSuffix = true): string[] => { - let node: any = root; - const matches: string[] = []; + let node: TrieNode = root; let token: string; for (let i = suffix.length - 1; i >= 0; i--) { token = suffix[i]; - node = node[token]; + const n = node[token]; + if (n === undefined) return []; + // if (n === true) return []; - if (node == null) return matches; + node = n; } + const matches: string[] = []; + // Performing DFS from prefix - const nodeStack: any[] = [node]; + const nodeStack: TrieNode[] = [node]; const suffixStack: string[] = [suffix]; let k: string; @@ -79,7 +92,7 @@ export const createTrie = (from?: string[] | Set) => { while (nodeStack.length) { $suffix = suffixStack.pop()!; - node = nodeStack.pop(); + node = nodeStack.pop()!; // eslint-disable-next-line guard-for-in -- plain object for (k in node) { @@ -91,7 +104,7 @@ export const createTrie = (from?: string[] | Set) => { continue; } - nodeStack.push(node[k]); + nodeStack.push(node[k]!); suffixStack.push(k + $suffix); } } @@ -106,29 +119,41 @@ export const createTrie = (from?: string[] | Set) => { * @return {boolean} */ const remove = (suffix: string): boolean => { - let node: any = root; - let toPrune: any = null; + let node: TrieNode = root; + let toPrune: TrieNode | null = null; let tokenToPrune: string | null = null; - let parent: any; + let parent: TrieNode = node; let token: string; for (let i = suffix.length - 1; i >= 0; i--) { token = suffix[i]; parent = node; - node = node[token]; + const n = node[token]; // Prefix does not exist - if (typeof node === 'undefined') { - return false; - } + if (n === undefined) return false; + // if (n === true) return false + + node = n; // Keeping track of a potential branch to prune + // If the node is to be pruned, but they are more than one token child in it, we can't prune it + // If there is only one token child, or no child at all, we can prune it safely + + let onlyChild = true; + for (const k in node) { + if (k !== token) { + onlyChild = false; + break; + } + } + if (toPrune !== null) { - if (Object.keys(node).length > 1) { + if (!onlyChild) { toPrune = null; tokenToPrune = null; } - } else if (Object.keys(node).length < 2) { + } else if (onlyChild) { toPrune = parent; tokenToPrune = token; } @@ -138,7 +163,7 @@ export const createTrie = (from?: string[] | Set) => { size--; - if (tokenToPrune) { + if (tokenToPrune && toPrune) { delete toPrune[tokenToPrune]; } else { delete node[SENTINEL]; @@ -154,14 +179,16 @@ export const createTrie = (from?: string[] | Set) => { * @return {boolean} */ const has = (suffix: string): boolean => { - let node: any = root; + let node: TrieNode = root; for (let i = suffix.length - 1; i >= 0; i--) { - node = node[suffix[i]]; - - if (typeof node === 'undefined') { + const n = node[suffix[i]]; + if (n === undefined) { return false; } + // if (n === true) return false; + + node = n; } return SENTINEL in node; @@ -184,265 +211,4 @@ export const createTrie = (from?: string[] | Set) => { }; }; -// class Trie { -// size = 0; -// root = {}; - -// /** -// * @param {string} suffix -// */ -// contains(suffix) { -// let node = this.root; -// let token; - -// for (let i = suffix.length - 1; i >= 0; i--) { -// token = suffix[i]; - -// node = node[token]; - -// if (node == null) return false; -// } - -// return true; -// } - -// /** -// * Method used to retrieve every item in the trie with the given prefix. -// * -// * @param {string} suffix - Prefix to query. -// * @param {boolean} [includeEqualWithSuffix] -// * @return {string[]} -// */ -// find(suffix, includeEqualWithSuffix = true) { -// let node = this.root; -// const matches = []; -// let token; - -// for (let i = suffix.length - 1; i >= 0; i--) { -// token = suffix[i]; - -// node = node[token]; - -// if (node == null) return matches; -// } - -// // Performing DFS from prefix -// const nodeStack = [node]; - -// const suffixStack = [suffix]; -// let k; - -// let $suffix = suffix; - -// while (nodeStack.length) { -// $suffix = suffixStack.pop(); -// node = nodeStack.pop(); - -// // eslint-disable-next-line guard-for-in -- plain object -// for (k in node) { -// if (k === SENTINEL) { -// if (includeEqualWithSuffix) { -// matches.push($suffix); -// } else if ($suffix !== suffix) { -// matches.push($suffix); -// } - -// continue; -// } - -// nodeStack.push(node[k]); -// suffixStack.push(k + $suffix); -// } -// } - -// return matches; -// } - -// // toJSON() { -// // return this.root; -// // } - -// /** -// * Method used to clear the trie. -// * -// * @return {void} -// */ -// // clear() { -// // // Properties -// // this.root = {}; -// // this.size = 0; -// // } - -// /** -// * Method used to update the value of the given prefix in the trie. -// * -// * @param {string|array} prefix - Prefix to follow. -// * @param {(oldValue: any | undefined) => any} updateFunction - Update value visitor callback. -// * @return {Trie} -// */ -// // update(prefix, updateFunction) { -// // let node = this.root; -// // let token; - -// // for (let i = 0, l = prefix.length; i < l; i++) { -// // token = prefix[i]; - -// // node = node[token] || (node[token] = {}); -// // } - -// // // Do we need to increase size? -// // if (!(SENTINEL in node)) -// // this.size++; - -// // node[SENTINEL] = updateFunction(node[SENTINEL]); - -// // return this; -// // } - -// /** -// * Method used to delete a prefix from the trie. -// * -// * @param {string} suffix - Prefix to delete. -// * @return {boolean} -// */ -// delete(suffix) { -// let node = this.root; -// let toPrune = null; -// let tokenToPrune = null; -// let parent; -// let token; - -// for (let i = suffix.length - 1; i >= 0; i--) { -// token = suffix[i]; -// parent = node; -// node = node[token]; - -// // Prefix does not exist -// if (typeof node === 'undefined') { -// return false; -// } - -// // Keeping track of a potential branch to prune -// if (toPrune !== null) { -// if (Object.keys(node).length > 1) { -// toPrune = null; -// tokenToPrune = null; -// } -// } else if (Object.keys(node).length < 2) { -// toPrune = parent; -// tokenToPrune = token; -// } -// } - -// if (!(SENTINEL in node)) return false; - -// this.size--; - -// if (toPrune) { -// delete toPrune[tokenToPrune]; -// } else { -// delete node[SENTINEL]; -// } - -// return true; -// } - -// /** -// * Method used to assert whether the given prefix exists in the Trie. -// * -// * @param {string} suffix - Prefix to check. -// * @return {boolean} -// */ -// has(suffix) { -// let node = this.root; -// let token; - -// for (let i = suffix.length - 1; i >= 0; i--) { -// token = suffix[i]; -// node = node[token]; - -// if (typeof node === 'undefined') { -// return false; -// } -// } - -// return SENTINEL in node; -// } - -// /** -// * @return {string[]} -// */ -// // dump() { -// // const node = this.root; -// // const nodeStack = []; -// // const prefixStack = []; -// // // Resolving initial prefix -// // const prefix = ''; - -// // nodeStack.push(node); -// // prefixStack.push(prefix); - -// // /** @type {string[]} */ -// // const results = []; - -// // let currentNode; -// // let currentPrefix; -// // let hasValue = false; -// // let k; - -// // while (nodeStack.length) { -// // currentNode = nodeStack.pop(); -// // currentPrefix = prefixStack.pop(); - -// // // eslint-disable-next-line guard-for-in -- plain object -// // for (k in currentNode) { -// // if (k === SENTINEL) { -// // hasValue = true; -// // continue; -// // } - -// // nodeStack.push(currentNode[k]); -// // prefixStack.push(k + currentPrefix); -// // } - -// // if (hasValue) results.push(currentPrefix); -// // } - -// // return results; -// // } - -// /** -// * Convenience known methods. -// */ -// // inspect() { -// // const proxy = new Set(); - -// // const iterator = this.prefixes(); -// // let step; - -// // while ((step = iterator.next(), !step.done)) -// // proxy.add(step.value); - -// // // Trick so that node displays the name of the constructor -// // Object.defineProperty(proxy, 'constructor', { -// // value: Trie, -// // enumerable: false -// // }); - -// // return proxy; -// // } -// /** -// * Static .from function taking an arbitrary iterable & converting it into -// * a trie. -// * -// * @param {string[] | Set} iterable - Target iterable. -// * @return {Trie} -// */ -// static from = iterable => { -// const trie = new Trie(); -// iterable.forEach(i => trie.add(i)); -// return trie; -// }; -// } - export default createTrie;