trie.ts 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. /**
  2. * Suffix Trie based on Mnemonist Trie
  3. */
  4. export const SENTINEL = Symbol('SENTINEL');
  5. type TrieNode = {
  6. [SENTINEL]: boolean
  7. } & Map<string, TrieNode>;
  8. const createNode = (): TrieNode => {
  9. const map = new Map<string, TrieNode>();
  10. const node = map as TrieNode;
  11. node[SENTINEL] = false;
  12. return node;
  13. };
  14. export const createTrie = (from?: string[] | Set<string>) => {
  15. let size = 0;
  16. const root: TrieNode = createNode();
  17. /**
  18. * Method used to add the given prefix to the trie.
  19. */
  20. const add = (suffix: string): void => {
  21. let node: TrieNode = root;
  22. let token: string;
  23. for (let i = suffix.length - 1; i >= 0; i--) {
  24. token = suffix[i];
  25. if (node.has(token)) {
  26. node = node.get(token)!;
  27. } else {
  28. const newNode = createNode();
  29. node.set(token, newNode);
  30. node = newNode;
  31. }
  32. }
  33. // Do we need to increase size?
  34. if (!node[SENTINEL]) {
  35. size++;
  36. node[SENTINEL] = true;
  37. }
  38. };
  39. /**
  40. * @param {string} suffix
  41. */
  42. const contains = (suffix: string): boolean => {
  43. let node: TrieNode | undefined = root;
  44. let token: string;
  45. for (let i = suffix.length - 1; i >= 0; i--) {
  46. token = suffix[i];
  47. node = node.get(token);
  48. if (!node) {
  49. return false;
  50. }
  51. }
  52. return true;
  53. };
  54. /**
  55. * Method used to retrieve every item in the trie with the given prefix.
  56. */
  57. const find = (inputSuffix: string, /** @default true */ includeEqualWithSuffix = true): string[] => {
  58. let node: TrieNode | undefined = root;
  59. let token: string;
  60. for (let i = inputSuffix.length - 1; i >= 0; i--) {
  61. token = inputSuffix[i];
  62. node = node.get(token);
  63. if (!node) {
  64. return [];
  65. }
  66. }
  67. const matches: string[] = [];
  68. // Performing DFS from prefix
  69. const nodeStack: TrieNode[] = [node];
  70. const suffixStack: string[] = [inputSuffix];
  71. while (nodeStack.length) {
  72. const suffix = suffixStack.pop()!;
  73. node = nodeStack.pop()!;
  74. if (node[SENTINEL]) {
  75. if (includeEqualWithSuffix || suffix !== inputSuffix) {
  76. matches.push(suffix);
  77. }
  78. }
  79. node.forEach((childNode, k) => {
  80. nodeStack.push(childNode);
  81. suffixStack.push(k + suffix);
  82. });
  83. }
  84. return matches;
  85. };
  86. /**
  87. * Method used to delete a prefix from the trie.
  88. */
  89. const remove = (suffix: string): boolean => {
  90. let node: TrieNode | undefined = root;
  91. let toPrune: TrieNode | null = null;
  92. let tokenToPrune: string | null = null;
  93. let parent: TrieNode = node;
  94. let token: string;
  95. for (let i = suffix.length - 1; i >= 0; i--) {
  96. token = suffix[i];
  97. parent = node;
  98. node = node.get(token);
  99. if (!node) {
  100. return false;
  101. }
  102. // Keeping track of a potential branch to prune
  103. // If the node is to be pruned, but they are more than one token child in it, we can't prune it
  104. // If there is only one token child, or no child at all, we can prune it safely
  105. const onlyChild = node.size === 1 && node.has(token);
  106. if (onlyChild) {
  107. toPrune = parent;
  108. tokenToPrune = token;
  109. } else if (toPrune !== null) { // not only child, retain the branch
  110. toPrune = null;
  111. tokenToPrune = null;
  112. }
  113. }
  114. if (!node[SENTINEL]) return false;
  115. size--;
  116. if (tokenToPrune && toPrune) {
  117. toPrune.delete(tokenToPrune);
  118. } else {
  119. node[SENTINEL] = false;
  120. }
  121. return true;
  122. };
  123. /**
  124. * Method used to assert whether the given prefix exists in the Trie.
  125. */
  126. const has = (suffix: string): boolean => {
  127. let node: TrieNode = root;
  128. for (let i = suffix.length - 1; i >= 0; i--) {
  129. const token = suffix[i];
  130. if (node.has(token)) {
  131. node = node.get(token)!;
  132. } else {
  133. return false;
  134. }
  135. }
  136. return node[SENTINEL];
  137. };
  138. if (from) {
  139. from.forEach(add);
  140. }
  141. return {
  142. add,
  143. contains,
  144. find,
  145. remove,
  146. delete: remove,
  147. has,
  148. get size() {
  149. return size;
  150. },
  151. get root() {
  152. return root;
  153. }
  154. };
  155. };
  156. export default createTrie;