-
Notifications
You must be signed in to change notification settings - Fork 1
/
index.ts
150 lines (133 loc) · 3.38 KB
/
index.ts
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
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
export interface Trie {
[prefix: string]: Trie | null;
}
export class CompactPrefixTree {
public words: Set<string>;
private T: Trie;
/**
* Create a CompactPrefixTree from the given list of words
*/
constructor(words: string[] = []) {
if (!Array.isArray(words)) {
throw new TypeError(`Expected string[], got ${typeof words}`);
}
this.words = new Set<string>();
this.T = {};
for (const word of words) {
this.add(word);
}
}
/**
* Add a word to the trie
*/
add(word: string) {
if (typeof word !== "string") {
throw new TypeError(`Expected string, got ${typeof word}`);
}
if (!word.length) return;
this.words.add(word);
add(word, this.T);
return this;
}
/**
* Get the longest prefix of word
*/
prefix(word: string) {
return getPrefix(word, this.T);
}
/**
* Get all entries from the trie
*/
get items() {
if (this.words.size && Object.keys(this.T)) {
return this.words;
}
return getWordsFromTrie(this.T);
}
}
export default CompactPrefixTree;
/**
* Add a word to Trie
*/
export function add(word: string, T: Trie | null): void {
let l = word.length;
if (!l) return;
// search for existing prefixes
while (l--) {
const prefix = word.substr(0, l + 1);
if (T !== null && T.hasOwnProperty(prefix)) {
// found prefix, move into subtrie
if (T[prefix] === null) {
// if one word is a pure subset of another word,
// the prefix should also point to the subset
T[prefix] = { "": null };
}
return add(word.substr(l + 1), T[prefix]);
}
}
if (T === null) throw new Error("Unexpected error.");
// no prefix found. insert word and check for prefix collision
const siblings = Object.keys(T);
l = word.length;
const hasSiblings = siblings.some(sibling => {
let s = 0;
while (s < l && sibling[s] == word[s]) s++;
const commonPrefix = s < l && s > 1 ? sibling.substr(0, s) : "";
if (commonPrefix) {
// rearrange the trie to move word with prefix collision
// into new common prefix subtrie
T[commonPrefix] = {};
add(sibling.substr(s), T[commonPrefix]);
// @ts-ignore
T[commonPrefix][sibling.substr(s)] = T[sibling];
add(word.substr(s), T[commonPrefix]);
delete T[sibling];
return true;
}
return false;
});
// no siblings at this level. take a new branch.
if (!hasSiblings) {
T[word] = null;
}
}
/**
* Get longest prefix of given word in Trie
*/
export function getPrefix(word: string, T: Trie | null) {
const len = word.length;
let prefix = "";
let i = 0;
while (T !== null && i < len) {
let key = "";
while (!T.hasOwnProperty(key) && i < len) {
key += word[i++];
}
if (!T.hasOwnProperty(key)) break;
prefix += key;
T = T[key] || null;
}
return { prefix, isProper: T === null };
}
/**
* Get all entries from the trie
*/
export function getWordsFromTrie(T: Trie) {
const words = new Set<string>();
_getWords(T, words, "");
return words;
}
/**
* Get all entries from the trie
*/
function _getWords(T: Trie | null, words: Set<string>, prefix: string) {
if (T === null) return;
for (const pre of Object.keys(T)) {
const word = prefix + pre;
words.add(word);
if (T.hasOwnProperty(pre) && T[pre] !== null) {
words.delete(word);
_getWords(T[pre], words, word);
}
}
}