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;
							 |