Skip to content

Commit 965fbd1

Browse files
Merge pull request #43 from amejiarosario/feature/trie
feat(trie): implement trie data structure
2 parents 567c110 + e31cc62 commit 965fbd1

File tree

5 files changed

+579
-0
lines changed

5 files changed

+579
-0
lines changed

Diff for: src/data-structures/trees/trie-1.js

+123
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
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+
* Search for complete word (by default) or partial if flag is set.
26+
* @param {string} word - Word to search.
27+
* @param {boolean} options.partial - Whether or not match partial matches.
28+
* @return {boolean}
29+
*/
30+
search(word, { partial } = {}) {
31+
let curr = this;
32+
33+
for (const char of word) {
34+
if (!curr.children[char]) { return false; }
35+
curr = curr.children[char];
36+
}
37+
38+
return partial ? true : curr.isWord;
39+
}
40+
41+
/**
42+
* Return true if any word on the trie starts with the given prefix
43+
* @param {string} prefix - Partial word to search.
44+
* @return {boolean}
45+
*/
46+
startsWith(prefix) {
47+
return this.search(prefix, { partial: true });
48+
}
49+
50+
/**
51+
* Returns all the words from the current `node`.
52+
* Uses backtracking.
53+
*
54+
* @param {string} prefix - The prefix to append to each word.
55+
* @param {string} node - Current node to start backtracking.
56+
* @param {string[]} words - Accumulated words.
57+
* @param {string} string - Current string.
58+
*/
59+
getAllWords(prefix = '', node = this, words = [], string = '') {
60+
if (node.isWord) {
61+
words.push(`${prefix}${string}`);
62+
}
63+
64+
for (const char of Object.keys(node.children)) {
65+
this.getAllWords(prefix, node.children[char], words, `${string}${char}`);
66+
}
67+
68+
return words;
69+
}
70+
71+
/**
72+
* Return true if found the word to be removed, otherwise false.
73+
* Iterative approach
74+
* @param {string} word - The word to remove
75+
* @returns {boolean}
76+
*/
77+
remove(word) {
78+
const stack = [];
79+
let curr = this;
80+
81+
for (const char of word) {
82+
if (!curr.children[char]) { return false; }
83+
stack.push(curr);
84+
curr = curr.children[char];
85+
}
86+
87+
if (!curr.isWord) { return false; }
88+
let node = stack.pop();
89+
90+
do {
91+
node.children = {};
92+
node = stack.pop();
93+
} while (node && !node.isWord);
94+
95+
return true;
96+
}
97+
98+
/**
99+
* Return true if found the word to be removed, otherwise false.
100+
* recursive approach
101+
* @param {string} word - The word to remove
102+
* @returns {boolean}
103+
*/
104+
remove2(word, i = 0, parent = this) {
105+
if (i === word.length - 1) {
106+
return true;
107+
}
108+
const child = parent.children[word.charAt(i)];
109+
if (!child) return false;
110+
111+
const found = this.remove(word, i + 1, child);
112+
113+
if (found) {
114+
delete parent.children[word.charAt(i)];
115+
}
116+
return true;
117+
}
118+
}
119+
120+
// Aliases
121+
Trie.prototype.add = Trie.prototype.insert;
122+
123+
module.exports = Trie;

Diff for: 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;

Diff for: src/data-structures/trees/trie.js

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

0 commit comments

Comments
 (0)