trie.ts 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448
  1. /**
  2. * Suffix Trie based on Mnemonist Trie
  3. */
  4. export const SENTINEL: string = String.fromCodePoint(0);
  5. /**
  6. * @param {string[] | Set<string>} [from]
  7. */
  8. export const createTrie = (from?: string[] | Set<string>) => {
  9. let size: number = 0;
  10. const root: any = {};
  11. /**
  12. * Method used to add the given prefix to the trie.
  13. *
  14. * @param {string} suffix - Prefix to follow.
  15. */
  16. const add = (suffix: string): void => {
  17. let node: any = root;
  18. let token: string;
  19. for (let i = suffix.length - 1; i >= 0; i--) {
  20. token = suffix[i];
  21. node[token] ||= {};
  22. node = node[token];
  23. }
  24. // Do we need to increase size?
  25. if (!(SENTINEL in node)) {
  26. size++;
  27. }
  28. node[SENTINEL] = true;
  29. };
  30. /**
  31. * @param {string} suffix
  32. */
  33. const contains = (suffix: string): boolean => {
  34. let node: any = root;
  35. let token: string;
  36. for (let i = suffix.length - 1; i >= 0; i--) {
  37. token = suffix[i];
  38. node = node[token];
  39. if (node == null) return false;
  40. }
  41. return true;
  42. };
  43. /**
  44. * Method used to retrieve every item in the trie with the given prefix.
  45. *
  46. * @param {string} suffix - Prefix to query.
  47. * @param {boolean} [includeEqualWithSuffix]
  48. * @return {string[]}
  49. */
  50. const find = (suffix: string, includeEqualWithSuffix: boolean = true): string[] => {
  51. let node: any = root;
  52. const matches: string[] = [];
  53. let token: string;
  54. for (let i = suffix.length - 1; i >= 0; i--) {
  55. token = suffix[i];
  56. node = node[token];
  57. if (node == null) return matches;
  58. }
  59. // Performing DFS from prefix
  60. const nodeStack: any[] = [node];
  61. const suffixStack: string[] = [suffix];
  62. let k: string;
  63. let $suffix: string = suffix;
  64. while (nodeStack.length) {
  65. $suffix = suffixStack.pop()!;
  66. node = nodeStack.pop();
  67. // eslint-disable-next-line guard-for-in -- plain object
  68. for (k in node) {
  69. if (k === SENTINEL) {
  70. if (includeEqualWithSuffix || $suffix !== suffix) {
  71. matches.push($suffix);
  72. }
  73. continue;
  74. }
  75. nodeStack.push(node[k]);
  76. suffixStack.push(k + $suffix);
  77. }
  78. }
  79. return matches;
  80. };
  81. /**
  82. * Method used to delete a prefix from the trie.
  83. *
  84. * @param {string} suffix - Prefix to delete.
  85. * @return {boolean}
  86. */
  87. const remove = (suffix: string): boolean => {
  88. let node: any = root;
  89. let toPrune: any = null;
  90. let tokenToPrune: string | null = null;
  91. let parent: any;
  92. let token: string;
  93. for (let i = suffix.length - 1; i >= 0; i--) {
  94. token = suffix[i];
  95. parent = node;
  96. node = node[token];
  97. // Prefix does not exist
  98. if (typeof node === 'undefined') {
  99. return false;
  100. }
  101. // Keeping track of a potential branch to prune
  102. if (toPrune !== null) {
  103. if (Object.keys(node).length > 1) {
  104. toPrune = null;
  105. tokenToPrune = null;
  106. }
  107. } else if (Object.keys(node).length < 2) {
  108. toPrune = parent;
  109. tokenToPrune = token;
  110. }
  111. }
  112. if (!(SENTINEL in node)) return false;
  113. size--;
  114. if (tokenToPrune) {
  115. delete toPrune[tokenToPrune];
  116. } else {
  117. delete node[SENTINEL];
  118. }
  119. return true;
  120. };
  121. /**
  122. * Method used to assert whether the given prefix exists in the Trie.
  123. *
  124. * @param {string} suffix - Prefix to check.
  125. * @return {boolean}
  126. */
  127. const has = (suffix: string): boolean => {
  128. let node: any = root;
  129. for (let i = suffix.length - 1; i >= 0; i--) {
  130. node = node[suffix[i]];
  131. if (typeof node === 'undefined') {
  132. return false;
  133. }
  134. }
  135. return SENTINEL in node;
  136. };
  137. if (from) {
  138. from.forEach(add);
  139. }
  140. return {
  141. add,
  142. contains,
  143. find,
  144. remove,
  145. delete: remove,
  146. has,
  147. get size() {
  148. return size;
  149. }
  150. };
  151. };
  152. // class Trie {
  153. // size = 0;
  154. // root = {};
  155. // /**
  156. // * @param {string} suffix
  157. // */
  158. // contains(suffix) {
  159. // let node = this.root;
  160. // let token;
  161. // for (let i = suffix.length - 1; i >= 0; i--) {
  162. // token = suffix[i];
  163. // node = node[token];
  164. // if (node == null) return false;
  165. // }
  166. // return true;
  167. // }
  168. // /**
  169. // * Method used to retrieve every item in the trie with the given prefix.
  170. // *
  171. // * @param {string} suffix - Prefix to query.
  172. // * @param {boolean} [includeEqualWithSuffix]
  173. // * @return {string[]}
  174. // */
  175. // find(suffix, includeEqualWithSuffix = true) {
  176. // let node = this.root;
  177. // const matches = [];
  178. // let token;
  179. // for (let i = suffix.length - 1; i >= 0; i--) {
  180. // token = suffix[i];
  181. // node = node[token];
  182. // if (node == null) return matches;
  183. // }
  184. // // Performing DFS from prefix
  185. // const nodeStack = [node];
  186. // const suffixStack = [suffix];
  187. // let k;
  188. // let $suffix = suffix;
  189. // while (nodeStack.length) {
  190. // $suffix = suffixStack.pop();
  191. // node = nodeStack.pop();
  192. // // eslint-disable-next-line guard-for-in -- plain object
  193. // for (k in node) {
  194. // if (k === SENTINEL) {
  195. // if (includeEqualWithSuffix) {
  196. // matches.push($suffix);
  197. // } else if ($suffix !== suffix) {
  198. // matches.push($suffix);
  199. // }
  200. // continue;
  201. // }
  202. // nodeStack.push(node[k]);
  203. // suffixStack.push(k + $suffix);
  204. // }
  205. // }
  206. // return matches;
  207. // }
  208. // // toJSON() {
  209. // // return this.root;
  210. // // }
  211. // /**
  212. // * Method used to clear the trie.
  213. // *
  214. // * @return {void}
  215. // */
  216. // // clear() {
  217. // // // Properties
  218. // // this.root = {};
  219. // // this.size = 0;
  220. // // }
  221. // /**
  222. // * Method used to update the value of the given prefix in the trie.
  223. // *
  224. // * @param {string|array} prefix - Prefix to follow.
  225. // * @param {(oldValue: any | undefined) => any} updateFunction - Update value visitor callback.
  226. // * @return {Trie}
  227. // */
  228. // // update(prefix, updateFunction) {
  229. // // let node = this.root;
  230. // // let token;
  231. // // for (let i = 0, l = prefix.length; i < l; i++) {
  232. // // token = prefix[i];
  233. // // node = node[token] || (node[token] = {});
  234. // // }
  235. // // // Do we need to increase size?
  236. // // if (!(SENTINEL in node))
  237. // // this.size++;
  238. // // node[SENTINEL] = updateFunction(node[SENTINEL]);
  239. // // return this;
  240. // // }
  241. // /**
  242. // * Method used to delete a prefix from the trie.
  243. // *
  244. // * @param {string} suffix - Prefix to delete.
  245. // * @return {boolean}
  246. // */
  247. // delete(suffix) {
  248. // let node = this.root;
  249. // let toPrune = null;
  250. // let tokenToPrune = null;
  251. // let parent;
  252. // let token;
  253. // for (let i = suffix.length - 1; i >= 0; i--) {
  254. // token = suffix[i];
  255. // parent = node;
  256. // node = node[token];
  257. // // Prefix does not exist
  258. // if (typeof node === 'undefined') {
  259. // return false;
  260. // }
  261. // // Keeping track of a potential branch to prune
  262. // if (toPrune !== null) {
  263. // if (Object.keys(node).length > 1) {
  264. // toPrune = null;
  265. // tokenToPrune = null;
  266. // }
  267. // } else if (Object.keys(node).length < 2) {
  268. // toPrune = parent;
  269. // tokenToPrune = token;
  270. // }
  271. // }
  272. // if (!(SENTINEL in node)) return false;
  273. // this.size--;
  274. // if (toPrune) {
  275. // delete toPrune[tokenToPrune];
  276. // } else {
  277. // delete node[SENTINEL];
  278. // }
  279. // return true;
  280. // }
  281. // /**
  282. // * Method used to assert whether the given prefix exists in the Trie.
  283. // *
  284. // * @param {string} suffix - Prefix to check.
  285. // * @return {boolean}
  286. // */
  287. // has(suffix) {
  288. // let node = this.root;
  289. // let token;
  290. // for (let i = suffix.length - 1; i >= 0; i--) {
  291. // token = suffix[i];
  292. // node = node[token];
  293. // if (typeof node === 'undefined') {
  294. // return false;
  295. // }
  296. // }
  297. // return SENTINEL in node;
  298. // }
  299. // /**
  300. // * @return {string[]}
  301. // */
  302. // // dump() {
  303. // // const node = this.root;
  304. // // const nodeStack = [];
  305. // // const prefixStack = [];
  306. // // // Resolving initial prefix
  307. // // const prefix = '';
  308. // // nodeStack.push(node);
  309. // // prefixStack.push(prefix);
  310. // // /** @type {string[]} */
  311. // // const results = [];
  312. // // let currentNode;
  313. // // let currentPrefix;
  314. // // let hasValue = false;
  315. // // let k;
  316. // // while (nodeStack.length) {
  317. // // currentNode = nodeStack.pop();
  318. // // currentPrefix = prefixStack.pop();
  319. // // // eslint-disable-next-line guard-for-in -- plain object
  320. // // for (k in currentNode) {
  321. // // if (k === SENTINEL) {
  322. // // hasValue = true;
  323. // // continue;
  324. // // }
  325. // // nodeStack.push(currentNode[k]);
  326. // // prefixStack.push(k + currentPrefix);
  327. // // }
  328. // // if (hasValue) results.push(currentPrefix);
  329. // // }
  330. // // return results;
  331. // // }
  332. // /**
  333. // * Convenience known methods.
  334. // */
  335. // // inspect() {
  336. // // const proxy = new Set();
  337. // // const iterator = this.prefixes();
  338. // // let step;
  339. // // while ((step = iterator.next(), !step.done))
  340. // // proxy.add(step.value);
  341. // // // Trick so that node displays the name of the constructor
  342. // // Object.defineProperty(proxy, 'constructor', {
  343. // // value: Trie,
  344. // // enumerable: false
  345. // // });
  346. // // return proxy;
  347. // // }
  348. // /**
  349. // * Static .from function taking an arbitrary iterable & converting it into
  350. // * a trie.
  351. // *
  352. // * @param {string[] | Set<string>} iterable - Target iterable.
  353. // * @return {Trie}
  354. // */
  355. // static from = iterable => {
  356. // const trie = new Trie();
  357. // iterable.forEach(i => trie.add(i));
  358. // return trie;
  359. // };
  360. // }
  361. export default createTrie;