trie.ts 4.1 KB

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