Skip to content

Commit fd76ed5

Browse files
committed
feat(trie): feature complete
1 parent a81f6e1 commit fd76ed5

File tree

4 files changed

+187
-2
lines changed

4 files changed

+187
-2
lines changed

src/data-structures/trees/trie-2.js

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
class Trie {
2+
constructor(val) {
3+
this.val = val;
4+
this.children = {};
5+
this.isWord = false;
6+
}
7+
8+
/**
9+
* Insert word into trie and mark last element as such.
10+
* @param {string} word
11+
* @return {undefined}
12+
*/
13+
insert(word) {
14+
let curr = this;
15+
16+
for (const char of word) {
17+
curr.children[char] = curr.children[char] || new Trie(char);
18+
curr = curr.children[char];
19+
}
20+
21+
curr.isWord = true;
22+
}
23+
24+
/**
25+
* Return true if found the word to be removed, otherwise false.
26+
* @param {string} word - The word to remove
27+
* @returns {boolean}
28+
*/
29+
remove(word) {
30+
return this.removeHelper(word);
31+
}
32+
33+
/**
34+
* Remove word from trie, return true if found, otherwise false.
35+
* @param {string} word - The word to remove.
36+
* @param {Trie} parent - The parent node.
37+
* @param {number} index - The index.
38+
* @param {number} meta.stop - Keeps track of the last letter that won't be removed.
39+
* @returns {boolean}
40+
*/
41+
removeHelper(word, parent = this, index = 0, meta = { stop: 0 }) {
42+
if (index === word.length) {
43+
parent.isWord = false;
44+
if (Object.keys(parent.children)) { meta.stop = index; }
45+
return true;
46+
}
47+
const child = parent.children[word.charAt(index)];
48+
if (!child) { return false; }
49+
if (parent.isWord) { meta.stop = index; }
50+
const found = this.removeHelper(word, child, index + 1, meta);
51+
// deletes all the nodes beyond `meta.stop`.
52+
if (found && index >= meta.stop) {
53+
delete parent.children[word.charAt(index)];
54+
}
55+
return found;
56+
}
57+
58+
/**
59+
* Remove word from trie, return true if found, otherwise false.
60+
* @param {string} word - The word to remove.
61+
* @param {Trie} parent - The parent node.
62+
* @param {number} index - The index.
63+
* @param {number} meta.stop - Keeps track of the last letter that won't be removed.
64+
* @returns {boolean}
65+
*/
66+
removeHelper(word, parent = this, index = 0, meta = { stop: 0 }) {
67+
if (index === word.length) {
68+
parent.isWord = false;
69+
if (Object.keys(parent.children)) { meta.stop = index; }
70+
return true;
71+
}
72+
const child = parent.children[word.charAt(index)];
73+
if (!child) { return false; }
74+
if (parent.isWord) { meta.stop = index; }
75+
const found = this.removeHelper(word, child, index + 1, meta);
76+
// deletes all the nodes beyond `meta.stop`.
77+
if (found && index >= meta.stop) {
78+
delete parent.children[word.charAt(index)];
79+
}
80+
return found;
81+
}
82+
83+
/**
84+
* Retun last node that matches word or prefix or false if not found.
85+
* @param {string} word - Word to search.
86+
* @param {boolean} options.partial - Whether or not match partial matches.
87+
* @return {Trie|false}
88+
*/
89+
searchNode(word) {
90+
let curr = this;
91+
92+
for (const char of word) {
93+
if (!curr.children[char]) { return false; }
94+
curr = curr.children[char];
95+
}
96+
97+
return curr;
98+
}
99+
100+
/**
101+
* Search for complete word (by default) or partial if flag is set.
102+
* @param {string} word - Word to search.
103+
* @param {boolean} options.partial - Whether or not match partial matches.
104+
* @return {boolean}
105+
*/
106+
search(word, { partial } = {}) {
107+
const curr = this.searchNode(word);
108+
if (!curr) { return false; }
109+
return partial ? true : curr.isWord;
110+
}
111+
112+
/**
113+
* Return true if any word on the trie starts with the given prefix
114+
* @param {string} prefix - Partial word to search.
115+
* @return {boolean}
116+
*/
117+
startsWith(prefix) {
118+
return this.search(prefix, { partial: true });
119+
}
120+
121+
/**
122+
* Returns all the words from the current `node`.
123+
* Uses backtracking.
124+
*
125+
* @param {string} prefix - The prefix to append to each word.
126+
* @param {string} node - Current node to start backtracking.
127+
*/
128+
getAllWords(prefix = '', node = this) {
129+
let words = [];
130+
131+
if (!node) { return words; }
132+
if (node.isWord) {
133+
words.push(prefix);
134+
}
135+
136+
for (const char of Object.keys(node.children)) {
137+
const newWords = this.getAllWords(`${prefix}${char}`, node.children[char]);
138+
words = words.concat(newWords);
139+
}
140+
141+
return words;
142+
}
143+
144+
/**
145+
* Return a list of words matching the prefix
146+
* @param {*} prefix - The prefix to match.
147+
* @returns {string[]}
148+
*/
149+
autocomplete(prefix = '') {
150+
const curr = this.searchNode(prefix);
151+
return this.getAllWords(prefix, curr);
152+
}
153+
}
154+
155+
// Aliases
156+
Trie.prototype.add = Trie.prototype.insert;
157+
158+
module.exports = Trie;

src/data-structures/trees/trie.js

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,31 @@ class Trie {
2727
* @returns {boolean}
2828
*/
2929
remove(word) {
30-
return this.removeHelper(word);
30+
let curr = this;
31+
// let lastWordToKeep = 0;
32+
const stack = [curr];
33+
34+
// find word and stack path
35+
for (const char of word) {
36+
if (!curr.children[char]) { return false; }
37+
// lastWordToKeep += 1;
38+
curr = curr.children[char];
39+
stack.push(curr);
40+
}
41+
42+
let child = stack.pop();
43+
child.isWord = false;
44+
45+
// remove non words without children
46+
while (stack.length) {
47+
const parent = stack.pop();
48+
if (!child.isWord && !Object.keys(child.children).length) {
49+
delete parent.children[child.val];
50+
}
51+
child = parent;
52+
}
53+
54+
return true;
3155
}
3256

3357
/**

src/data-structures/trees/trie.spec.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ describe('Trie', () => {
137137
});
138138
});
139139

140-
describe('remove', () => {
140+
fdescribe('remove', () => {
141141
it('should remove a word', () => {
142142
trie = new Trie();
143143
trie.insert('a');

src/index.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ const BinaryTreeNode = require('./data-structures/trees/binary-tree-node');
1414
const AvlTree = require('./data-structures/trees/avl-tree');
1515
const RedBlackTree = require('./data-structures/trees/red-black-tree');
1616
const LRUCache = require('./data-structures/custom/lru-cache');
17+
const Trie = require('./data-structures/trees/trie');
18+
1719
// algorithms
1820
const bubbleSort = require('./algorithms/sorting/bubble-sort');
1921
const insertionSort = require('./algorithms/sorting/insertion-sort');
@@ -37,6 +39,7 @@ module.exports = {
3739
AvlTree,
3840
RedBlackTree,
3941
LRUCache,
42+
Trie,
4043
bubbleSort,
4144
insertionSort,
4245
selectionSort,

0 commit comments

Comments
 (0)