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