-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathtrie.js
131 lines (111 loc) · 3.31 KB
/
trie.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
// prettier-ignore
const alphabet = [
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i',
'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r',
's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
];
/**
* @typedef {{children: Trie[], isEndOfWord?: boolean, checked?: string }} Trie
*/
/**
* Creates a new dictionary from the text
* @param {string} text
* @returns {Trie}
*/
function parse(text) {
const words = text.split('\n').filter((t) => t.length > 0);
return insertAll(words);
}
/**
* Creates a new dictionary from a list of words
* @param {string[]} words
* @returns {Trie}
*/
function insertAll(words) {
const dictionary = { children: new Array(26) };
words.forEach((word) => {
insert(dictionary, word);
});
return dictionary;
}
/**
* Adds a word to the dictionary
* @param {Trie} dictionary
* @param {string} word
*/
function insert(dictionary, word) {
let current = dictionary;
// For each character in the word...
for (let i = 0; i < word.length; i++) {
// Create a new child dictionary for this character
const index = alphabet.indexOf(word[i]);
if (!current.children[index]) {
current.children[index] = { isEndOfWord: false, children: new Array(26) };
}
// Update the current child dictionary
current = current.children[index];
}
// The deepest child dictionary represents the last character in the word
current.isEndOfWord = true;
}
/**
* Returns true if the tree contains the prefix
* @param {Trie} tree
* @param {string} prefix
* @returns
*/
function hasPrefix(tree, prefix) {
let node = tree;
// For each character in the prefix...
for (let i = 0; i < prefix.length; i++) {
const index = alphabet.indexOf(prefix[i]);
// If there is no child tree, there
// are no words starting with the prefix
if (!node.children[index]) {
node.checked = 'failed';
return false;
}
node = node.children[index];
node.checked = 'passed';
}
// If child trees exist till the end of the
// prefix, then the tree contains the prefix!
return true;
}
/**
* Returns all the words in the dictionary that start with the prefix
* @param {Trie} dictionary
* @param {string} prefix
* @returns
*/
function startsWith(dictionary, prefix) {
let current = dictionary;
// For each character in the prefix...
for (let i = 0; i < prefix.length; i++) {
const index = alphabet.indexOf(prefix[i]);
// If there is no child dictionary, there
// are no words starting with the prefix
if (!current.children[index]) return [];
current = current.children[index];
}
// At the end of the prefix, we collect the words
// in the current child dictionary and its children
const matches = [];
collectWords(current, prefix, matches);
return matches;
}
/**
* Collects all the words in the dictionary, prefixing them with `currentWord`
* @param {Trie} dictionary
* @param {string} currentWord
* @param {string[]} words
*/
function collectWords(dictionary, currentWord, words) {
// If the current dictionary is the end of the word, collect the word
if (dictionary.isEndOfWord) words.push(currentWord);
// Collect the words from each child dictionary
dictionary.children.forEach((childNode, i) => {
collectWords(childNode, currentWord + alphabet[i], words);
});
}
module.exports = { insert, startsWith, hasPrefix, alphabet, parse, insertAll };