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.
181 lines
3.2 KiB
181 lines
3.2 KiB
3 years ago
|
/* eslint no-constant-condition: 0 */
|
||
|
/**
|
||
|
* Mnemonist BK Tree
|
||
|
* ==================
|
||
|
*
|
||
|
* Implementation of a Burkhard-Keller tree, allowing fast lookups of words
|
||
|
* that lie within a specified distance of the query word.
|
||
|
*
|
||
|
* [Reference]:
|
||
|
* https://en.wikipedia.org/wiki/BK-tree
|
||
|
*
|
||
|
* [Article]:
|
||
|
* W. Burkhard and R. Keller. Some approaches to best-match file searching,
|
||
|
* CACM, 1973
|
||
|
*/
|
||
|
var forEach = require('obliterator/foreach');
|
||
|
|
||
|
/**
|
||
|
* BK Tree.
|
||
|
*
|
||
|
* @constructor
|
||
|
* @param {function} distance - Distance function to use.
|
||
|
*/
|
||
|
function BKTree(distance) {
|
||
|
|
||
|
if (typeof distance !== 'function')
|
||
|
throw new Error('mnemonist/BKTree.constructor: given `distance` should be a function.');
|
||
|
|
||
|
this.distance = distance;
|
||
|
this.clear();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Method used to add an item to the tree.
|
||
|
*
|
||
|
* @param {any} item - Item to add.
|
||
|
* @return {BKTree}
|
||
|
*/
|
||
|
BKTree.prototype.add = function(item) {
|
||
|
|
||
|
// Initializing the tree with the first given word
|
||
|
if (!this.root) {
|
||
|
this.root = {
|
||
|
item: item,
|
||
|
children: {}
|
||
|
};
|
||
|
|
||
|
this.size++;
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
var node = this.root,
|
||
|
d;
|
||
|
|
||
|
while (true) {
|
||
|
d = this.distance(item, node.item);
|
||
|
|
||
|
if (!node.children[d])
|
||
|
break;
|
||
|
|
||
|
node = node.children[d];
|
||
|
}
|
||
|
|
||
|
node.children[d] = {
|
||
|
item: item,
|
||
|
children: {}
|
||
|
};
|
||
|
|
||
|
this.size++;
|
||
|
return this;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Method used to query the tree.
|
||
|
*
|
||
|
* @param {number} n - Maximum distance between query & item.
|
||
|
* @param {any} query - Query
|
||
|
* @return {BKTree}
|
||
|
*/
|
||
|
BKTree.prototype.search = function(n, query) {
|
||
|
if (!this.root)
|
||
|
return [];
|
||
|
|
||
|
var found = [],
|
||
|
stack = [this.root],
|
||
|
node,
|
||
|
child,
|
||
|
d,
|
||
|
i,
|
||
|
l;
|
||
|
|
||
|
while (stack.length) {
|
||
|
node = stack.pop();
|
||
|
d = this.distance(query, node.item);
|
||
|
|
||
|
if (d <= n)
|
||
|
found.push({item: node.item, distance: d});
|
||
|
|
||
|
for (i = d - n, l = d + n + 1; i < l; i++) {
|
||
|
child = node.children[i];
|
||
|
|
||
|
if (child)
|
||
|
stack.push(child);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return found;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Method used to clear the tree.
|
||
|
*
|
||
|
* @return {undefined}
|
||
|
*/
|
||
|
BKTree.prototype.clear = function() {
|
||
|
|
||
|
// Properties
|
||
|
this.size = 0;
|
||
|
this.root = null;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Convenience known methods.
|
||
|
*/
|
||
|
BKTree.prototype.toJSON = function() {
|
||
|
return this.root;
|
||
|
};
|
||
|
|
||
|
BKTree.prototype.inspect = function() {
|
||
|
var array = [],
|
||
|
stack = [this.root],
|
||
|
node,
|
||
|
d;
|
||
|
|
||
|
while (stack.length) {
|
||
|
node = stack.pop();
|
||
|
|
||
|
if (!node)
|
||
|
continue;
|
||
|
|
||
|
array.push(node.item);
|
||
|
|
||
|
for (d in node.children)
|
||
|
stack.push(node.children[d]);
|
||
|
}
|
||
|
|
||
|
// Trick so that node displays the name of the constructor
|
||
|
Object.defineProperty(array, 'constructor', {
|
||
|
value: BKTree,
|
||
|
enumerable: false
|
||
|
});
|
||
|
|
||
|
return array;
|
||
|
};
|
||
|
|
||
|
if (typeof Symbol !== 'undefined')
|
||
|
BKTree.prototype[Symbol.for('nodejs.util.inspect.custom')] = BKTree.prototype.inspect;
|
||
|
|
||
|
/**
|
||
|
* Static @.from function taking an arbitrary iterable & converting it into
|
||
|
* a tree.
|
||
|
*
|
||
|
* @param {Iterable} iterable - Target iterable.
|
||
|
* @param {function} distance - Distance function.
|
||
|
* @return {Heap}
|
||
|
*/
|
||
|
BKTree.from = function(iterable, distance) {
|
||
|
var tree = new BKTree(distance);
|
||
|
|
||
|
forEach(iterable, function(value) {
|
||
|
tree.add(value);
|
||
|
});
|
||
|
|
||
|
return tree;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Exporting.
|
||
|
*/
|
||
|
module.exports = BKTree;
|