| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456 |
- /**
- * Suffix Trie based on Mnemonist Trie
- */
- const SENTINEL = String.fromCodePoint(0);
- /**
- * @param {string[] | Set<string>} [from]
- */
- const createTrie = (from) => {
- let size = 0;
- const root = {};
- /**
- * Method used to add the given prefix to the trie.
- *
- * @param {string} suffix - Prefix to follow.
- */
- const add = (suffix) => {
- let node = root;
- let token;
- for (let i = suffix.length - 1; i >= 0; i--) {
- token = suffix[i];
- node[token] ||= {};
- node = node[token];
- }
- // Do we need to increase size?
- if (!(SENTINEL in node)) {
- size++;
- }
- node[SENTINEL] = true;
- };
- /**
- * @param {string} suffix
- */
- const contains = (suffix) => {
- let node = 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[]}
- */
- const find = (suffix, includeEqualWithSuffix = true) => {
- let node = 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;
- };
- /**
- * Method used to delete a prefix from the trie.
- *
- * @param {string} suffix - Prefix to delete.
- * @return {boolean}
- */
- const remove = (suffix) => {
- let node = 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;
- 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}
- */
- const has = (suffix) => {
- let node = 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;
- };
- if (from) {
- from.forEach(add);
- }
- return {
- add,
- contains,
- find,
- remove,
- delete: remove,
- has,
- get size() {
- return size;
- }
- };
- };
- // 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<string>} iterable - Target iterable.
- // * @return {Trie}
- // */
- // static from = iterable => {
- // const trie = new Trie();
- // iterable.forEach(i => trie.add(i));
- // return trie;
- // };
- // }
- /**
- * Exporting.
- */
- module.exports.SENTINEL = SENTINEL;
- module.exports = createTrie;
|