Skip to content

Commit e31cc62

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

File tree

4 files changed

+159
-24
lines changed

4 files changed

+159
-24
lines changed

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

+133
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
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+
* Retun last node that matches word or prefix or false if not found.
60+
* @param {string} word - Word to search.
61+
* @param {boolean} options.partial - Whether or not match partial matches.
62+
* @return {Trie|false}
63+
*/
64+
searchNode(word) {
65+
let curr = this;
66+
67+
for (const char of word) {
68+
if (!curr.children[char]) { return false; }
69+
curr = curr.children[char];
70+
}
71+
72+
return curr;
73+
}
74+
75+
/**
76+
* Search for complete word (by default) or partial if flag is set.
77+
* @param {string} word - Word to search.
78+
* @param {boolean} options.partial - Whether or not match partial matches.
79+
* @return {boolean}
80+
*/
81+
search(word, { partial } = {}) {
82+
const curr = this.searchNode(word);
83+
if (!curr) { return false; }
84+
return partial ? true : curr.isWord;
85+
}
86+
87+
/**
88+
* Return true if any word on the trie starts with the given prefix
89+
* @param {string} prefix - Partial word to search.
90+
* @return {boolean}
91+
*/
92+
startsWith(prefix) {
93+
return this.search(prefix, { partial: true });
94+
}
95+
96+
/**
97+
* Returns all the words from the current `node`.
98+
* Uses backtracking.
99+
*
100+
* @param {string} prefix - The prefix to append to each word.
101+
* @param {string} node - Current node to start backtracking.
102+
*/
103+
getAllWords(prefix = '', node = this) {
104+
let words = [];
105+
106+
if (!node) { return words; }
107+
if (node.isWord) {
108+
words.push(prefix);
109+
}
110+
111+
for (const char of Object.keys(node.children)) {
112+
const newWords = this.getAllWords(`${prefix}${char}`, node.children[char]);
113+
words = words.concat(newWords);
114+
}
115+
116+
return words;
117+
}
118+
119+
/**
120+
* Return a list of words matching the prefix
121+
* @param {*} prefix - The prefix to match.
122+
* @returns {string[]}
123+
*/
124+
autocomplete(prefix = '') {
125+
const curr = this.searchNode(prefix);
126+
return this.getAllWords(prefix, curr);
127+
}
128+
}
129+
130+
// Aliases
131+
Trie.prototype.add = Trie.prototype.insert;
132+
133+
module.exports = Trie;

src/data-structures/trees/trie.js

+22-23
Original file line numberDiff line numberDiff line change
@@ -27,32 +27,31 @@ class Trie {
2727
* @returns {boolean}
2828
*/
2929
remove(word) {
30-
return this.removeHelper(word);
31-
}
30+
let curr = this;
31+
// let lastWordToKeep = 0;
32+
const stack = [curr];
3233

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;
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);
4640
}
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)];
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;
5452
}
55-
return found;
53+
54+
return true;
5655
}
5756

5857
/**

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

+1-1
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

+3
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)