You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

168 lines
3.4 KiB

3 years ago
/**
* Mnemonist Trie
* ===============
*
* JavaScript Trie implementation based upon plain objects. As such this
* structure is more a convenience building upon the trie's advantages than
* a real performant alternative to already existing structures.
*
* Note that the Trie is based upon the TrieMap since the underlying machine
* is the very same. The Trie just does not let you set values and only
* considers the existence of the given prefixes.
*/
var forEach = require('obliterator/foreach'),
TrieMap = require('./trie-map.js');
/**
* Constants.
*/
var SENTINEL = String.fromCharCode(0);
/**
* Trie.
*
* @constructor
*/
function Trie(Token) {
this.mode = Token === Array ? 'array' : 'string';
this.clear();
}
// Re-using TrieMap's prototype
for (var methodName in TrieMap.prototype)
Trie.prototype[methodName] = TrieMap.prototype[methodName];
// Dropping irrelevant methods
delete Trie.prototype.set;
delete Trie.prototype.get;
delete Trie.prototype.values;
delete Trie.prototype.entries;
/**
* Method used to add the given prefix to the trie.
*
* @param {string|array} prefix - Prefix to follow.
* @return {TrieMap}
*/
Trie.prototype.add = function(prefix) {
var node = this.root,
token;
for (var i = 0, l = prefix.length; i < l; i++) {
token = prefix[i];
node = node[token] || (node[token] = {});
}
// Do we need to increase size?
if (!(SENTINEL in node))
this.size++;
node[SENTINEL] = true;
return this;
};
/**
* Method used to retrieve every item in the trie with the given prefix.
*
* @param {string|array} prefix - Prefix to query.
* @return {array}
*/
Trie.prototype.find = function(prefix) {
var isString = typeof prefix === 'string';
var node = this.root,
matches = [],
token,
i,
l;
for (i = 0, l = prefix.length; i < l; i++) {
token = prefix[i];
node = node[token];
if (typeof node === 'undefined')
return matches;
}
// Performing DFS from prefix
var nodeStack = [node],
prefixStack = [prefix],
k;
while (nodeStack.length) {
prefix = prefixStack.pop();
node = nodeStack.pop();
for (k in node) {
if (k === SENTINEL) {
matches.push(prefix);
continue;
}
nodeStack.push(node[k]);
prefixStack.push(isString ? prefix + k : prefix.concat(k));
}
}
return matches;
};
/**
* Attaching the #.keys method to Symbol.iterator if possible.
*/
if (typeof Symbol !== 'undefined')
Trie.prototype[Symbol.iterator] = Trie.prototype.keys;
/**
* Convenience known methods.
*/
Trie.prototype.inspect = function() {
var proxy = new Set();
var iterator = this.keys(),
step;
while ((step = iterator.next(), !step.done))
proxy.add(step.value);
// Trick so that node displays the name of the constructor
Object.defineProperty(proxy, 'constructor', {
value: Trie,
enumerable: false
});
return proxy;
};
if (typeof Symbol !== 'undefined')
Trie.prototype[Symbol.for('nodejs.util.inspect.custom')] = Trie.prototype.inspect;
Trie.prototype.toJSON = function() {
return this.root;
};
/**
* Static @.from function taking an arbitrary iterable & converting it into
* a trie.
*
* @param {Iterable} iterable - Target iterable.
* @return {Trie}
*/
Trie.from = function(iterable) {
var trie = new Trie();
forEach(iterable, function(value) {
trie.add(value);
});
return trie;
};
/**
* Exporting.
*/
Trie.SENTINEL = SENTINEL;
module.exports = Trie;