compact-prefix-tree
Version:
A serializable compact prefix trie
143 lines (142 loc) • 4.46 kB
JavaScript
define(["require", "exports"], function (require, exports) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
class CompactPrefixTree {
/**
* Create a CompactPrefixTree from the given list of words
*/
constructor(words = []) {
if (!Array.isArray(words)) {
throw new TypeError(`Expected string[], got ${typeof words}`);
}
this.words = new Set();
this.T = {};
for (const word of words) {
this.add(word);
}
}
/**
* Add a word to the trie
*/
add(word) {
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) {
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);
}
}
exports.CompactPrefixTree = CompactPrefixTree;
exports.default = CompactPrefixTree;
/**
* Add a word to Trie
*/
function add(word, T) {
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;
}
}
exports.add = add;
/**
* Get longest prefix of given word in Trie
*/
function getPrefix(word, T) {
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 };
}
exports.getPrefix = getPrefix;
/**
* Get all entries from the trie
*/
function getWordsFromTrie(T) {
const words = new Set();
_getWords(T, words, "");
return words;
}
exports.getWordsFromTrie = getWordsFromTrie;
/**
* Get all entries from the trie
*/
function _getWords(T, words, prefix) {
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);
}
}
}
});