trie.js 9.6 KB

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