Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit a0e0fd8

Browse files
feat(trie): implement trie data structure
1 parent bbe69dd commit a0e0fd8

File tree

3 files changed

+306
-0
lines changed

3 files changed

+306
-0
lines changed

‎src/data-structures/trees/trie-1.js

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
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+
// Aliases
73+
Trie.prototype.add = Trie.prototype.insert;
74+
75+
module.exports = Trie;

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

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
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+
* Retun last node that matches word or prefix or false if not found.
26+
* @param {string} word - Word to search.
27+
* @param {boolean} options.partial - Whether or not match partial matches.
28+
* @return {Trie|false}
29+
*/
30+
searchNode(word) {
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 curr;
39+
}
40+
41+
/**
42+
* Search for complete word (by default) or partial if flag is set.
43+
* @param {string} word - Word to search.
44+
* @param {boolean} options.partial - Whether or not match partial matches.
45+
* @return {boolean}
46+
*/
47+
search(word, { partial } = {}) {
48+
const curr = this.searchNode(word);
49+
if (!curr) { return false; }
50+
return partial ? true : curr.isWord;
51+
}
52+
53+
/**
54+
* Return true if any word on the trie starts with the given prefix
55+
* @param {string} prefix - Partial word to search.
56+
* @return {boolean}
57+
*/
58+
startsWith(prefix) {
59+
return this.search(prefix, { partial: true });
60+
}
61+
62+
/**
63+
* Returns all the words from the current `node`.
64+
* Uses backtracking.
65+
*
66+
* @param {string} prefix - The prefix to append to each word.
67+
* @param {string} node - Current node to start backtracking.
68+
* @param {string[]} words - Accumulated words.
69+
* @param {string} string - Current string.
70+
*/
71+
getAllWords(prefix = '', node = this, words = [], string = '') {
72+
if (!node) { return words; }
73+
if (node.isWord) {
74+
words.push(`${prefix}${string}`);
75+
}
76+
77+
for (const char of Object.keys(node.children)) {
78+
this.getAllWords(prefix, node.children[char], words, `${string}${char}`);
79+
}
80+
81+
return words;
82+
}
83+
84+
/**
85+
* Return a list of words matching the prefix
86+
* @param {*} prefix - The prefix to match.
87+
* @returns {string[]}
88+
*/
89+
autocomplete(prefix = '') {
90+
const curr = this.searchNode(prefix);
91+
return this.getAllWords(prefix, curr);
92+
}
93+
}
94+
95+
// Aliases
96+
Trie.prototype.add = Trie.prototype.insert;
97+
98+
module.exports = Trie;

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

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
const Trie = require('./trie');
2+
3+
describe('Trie', () => {
4+
let trie;
5+
6+
beforeEach(() => {
7+
trie = new Trie();
8+
});
9+
10+
describe('construtor', () => {
11+
it('should initialize trie', () => {
12+
expect(trie).toBeDefined();
13+
});
14+
15+
it('should set default value to undefined', () => {
16+
expect(trie.val).toEqual(undefined);
17+
});
18+
19+
it('should initialization value', () => {
20+
trie = new Trie(1);
21+
expect(trie.val).toEqual(1);
22+
});
23+
24+
it('should initialize children as empty map', () => {
25+
expect(trie.children).toEqual({});
26+
});
27+
28+
it('should not be a word by default', () => {
29+
expect(trie.isWord).toEqual(false);
30+
});
31+
});
32+
33+
describe('insert', () => {
34+
it('should insert a word', () => {
35+
trie.insert('ab');
36+
expect(trie.children.a).toBeDefined();
37+
expect(trie.children.a.children.b).toBeDefined();
38+
expect(trie.children.a.isWord).toEqual(false);
39+
expect(trie.children.a.children.b.isWord).toEqual(true);
40+
});
41+
42+
it('should insert multiple words with the same root', () => {
43+
trie.insert('a');
44+
trie.insert('ab');
45+
expect(trie.children.a.isWord).toEqual(true);
46+
expect(trie.children.a.children.b.isWord).toEqual(true);
47+
});
48+
});
49+
50+
describe('search & startsWith', () => {
51+
beforeEach(() => {
52+
trie.insert('dog');
53+
trie.insert('dogs');
54+
trie.insert('door');
55+
});
56+
57+
it('should search for words', () => {
58+
expect(trie.search('dog')).toEqual(true);
59+
});
60+
61+
it('should not match incomplete words by default', () => {
62+
expect(trie.search('do')).toEqual(false);
63+
});
64+
65+
it('should match partial words if partial is set', () => {
66+
expect(trie.search('do', {
67+
partial: true,
68+
})).toEqual(true);
69+
expect(trie.startsWith('do')).toEqual(true);
70+
});
71+
72+
it('should not match non existing words', () => {
73+
expect(trie.search('doors')).toEqual(false);
74+
});
75+
76+
it('should not match non existing words with partials', () => {
77+
expect(trie.search('doors', {
78+
partial: true,
79+
})).toEqual(false);
80+
expect(trie.startsWith('doors')).toEqual(false);
81+
});
82+
});
83+
84+
describe('when multiple words are inserted', () => {
85+
beforeEach(() => {
86+
trie.insert('dog');
87+
trie.insert('dogs');
88+
trie.insert('door');
89+
trie.insert('day');
90+
trie.insert('cat');
91+
});
92+
93+
describe('getAllWords', () => {
94+
it('should get all words', () => {
95+
const words = trie.getAllWords();
96+
expect(words.length).toEqual(5);
97+
expect(words).toEqual(['dog', 'dogs', 'door', 'day', 'cat']);
98+
});
99+
100+
it('should use prefix', () => {
101+
const words = trie.getAllWords("Adrian's ");
102+
expect(words.length).toEqual(5);
103+
expect(words).toEqual([
104+
"Adrian's dog",
105+
"Adrian's dogs",
106+
"Adrian's door",
107+
"Adrian's day",
108+
"Adrian's cat",
109+
]);
110+
});
111+
});
112+
113+
describe('autocomplete', () => {
114+
it('should return all words if not prefix is given', () => {
115+
const words = trie.autocomplete();
116+
expect(words.length).toBe(5);
117+
expect(words).toEqual(['dog', 'dogs', 'door', 'day', 'cat']);
118+
});
119+
120+
it('should auto complete words given a prefix', () => {
121+
const words = trie.autocomplete('do');
122+
expect(words.length).toBe(3);
123+
expect(words).toEqual(['dog', 'dogs', 'door']);
124+
});
125+
126+
it('should handle non-existing words prefixes', () => {
127+
const words = trie.autocomplete('co');
128+
expect(words.length).toBe(0);
129+
expect(words).toEqual([]);
130+
});
131+
});
132+
});
133+
});

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /