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.
		
		
		
		
		
			
		
			
				
					
					
						
							428 lines
						
					
					
						
							8.7 KiB
						
					
					
				
			
		
		
	
	
							428 lines
						
					
					
						
							8.7 KiB
						
					
					
				/* eslint no-constant-condition: 0 */
 | 
						|
 | 
						|
/* eslint-disable */
 | 
						|
 | 
						|
/**
 | 
						|
 * Mnemonist FixedFixedCritBitTreeMap
 | 
						|
 * ===================================
 | 
						|
 *
 | 
						|
 * TODO...
 | 
						|
 *
 | 
						|
 * [References]:
 | 
						|
 * https://cr.yp.to/critbit.html
 | 
						|
 * https://www.imperialviolet.org/binary/critbit.pdf
 | 
						|
 */
 | 
						|
var bitwise = require('./utils/bitwise.js'),
 | 
						|
    typed = require('./utils/typed-arrays.js');
 | 
						|
 | 
						|
/**
 | 
						|
 * Helpers.
 | 
						|
 */
 | 
						|
 | 
						|
/**
 | 
						|
 * Helper returning the direction we need to take given a key and an
 | 
						|
 * encoded critbit.
 | 
						|
 *
 | 
						|
 * @param  {string} key     - Target key.
 | 
						|
 * @param  {number} critbit - Packed address of byte + mask.
 | 
						|
 * @return {number}         - 0, left or 1, right.
 | 
						|
 */
 | 
						|
function getDirection(key, critbit) {
 | 
						|
  var byteIndex = critbit >> 8;
 | 
						|
 | 
						|
  if (byteIndex > key.length - 1)
 | 
						|
    return 0;
 | 
						|
 | 
						|
  var byte = key.charCodeAt(byteIndex),
 | 
						|
      mask = critbit & 0xff;
 | 
						|
 | 
						|
  return byte & mask;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Helper returning the packed address of byte + mask or -1 if strings
 | 
						|
 * are identical.
 | 
						|
 *
 | 
						|
 * @param  {string} a      - First key.
 | 
						|
 * @param  {string} b      - Second key.
 | 
						|
 * @return {number}        - Packed address of byte + mask.
 | 
						|
 */
 | 
						|
function findCriticalBit(a, b) {
 | 
						|
  var i = 0,
 | 
						|
      tmp;
 | 
						|
 | 
						|
  // Swapping so a is the shortest
 | 
						|
  if (a.length > b.length) {
 | 
						|
    tmp = b;
 | 
						|
    b = a;
 | 
						|
    a = tmp;
 | 
						|
  }
 | 
						|
 | 
						|
  var l = a.length,
 | 
						|
      mask;
 | 
						|
 | 
						|
  while (i < l) {
 | 
						|
    if (a[i] !== b[i]) {
 | 
						|
      mask = bitwise.msb8(
 | 
						|
        a.charCodeAt(i) ^ b.charCodeAt(i)
 | 
						|
      );
 | 
						|
 | 
						|
      return (i << 8) | mask;
 | 
						|
    }
 | 
						|
 | 
						|
    i++;
 | 
						|
  }
 | 
						|
 | 
						|
  // Strings are identical
 | 
						|
  if (a.length === b.length)
 | 
						|
    return -1;
 | 
						|
 | 
						|
  // NOTE: x ^ 0 is the same as x
 | 
						|
  mask = bitwise.msb8(b.charCodeAt(i));
 | 
						|
 | 
						|
  return (i << 8) | mask;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * FixedCritBitTreeMap.
 | 
						|
 *
 | 
						|
 * @constructor
 | 
						|
 */
 | 
						|
function FixedCritBitTreeMap(capacity) {
 | 
						|
 | 
						|
  if (typeof capacity !== 'number' || capacity <= 0)
 | 
						|
    throw new Error('mnemonist/fixed-critbit-tree-map: `capacity` should be a positive number.');
 | 
						|
 | 
						|
  // Properties
 | 
						|
  this.capacity = capacity;
 | 
						|
  this.offset = 0;
 | 
						|
  this.root = 0;
 | 
						|
  this.size = 0;
 | 
						|
 | 
						|
  var PointerArray = typed.getSignedPointerArray(capacity + 1);
 | 
						|
 | 
						|
  this.keys = new Array(capacity);
 | 
						|
  this.values = new Array(capacity);
 | 
						|
  this.lefts = new PointerArray(capacity - 1);
 | 
						|
  this.rights = new PointerArray(capacity - 1);
 | 
						|
  this.critbits = new Uint32Array(capacity);
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Method used to clear the FixedCritBitTreeMap.
 | 
						|
 *
 | 
						|
 * @return {undefined}
 | 
						|
 */
 | 
						|
FixedCritBitTreeMap.prototype.clear = function() {
 | 
						|
 | 
						|
  // Properties
 | 
						|
  // TODO...
 | 
						|
  this.root = null;
 | 
						|
  this.size = 0;
 | 
						|
};
 | 
						|
 | 
						|
/**
 | 
						|
 * Method used to set the value of the given key in the trie.
 | 
						|
 *
 | 
						|
 * @param  {string}         key   - Key to set.
 | 
						|
 * @param  {any}            value - Arbitrary value.
 | 
						|
 * @return {FixedCritBitTreeMap}
 | 
						|
 */
 | 
						|
FixedCritBitTreeMap.prototype.set = function(key, value) {
 | 
						|
  var pointer;
 | 
						|
 | 
						|
  // TODO: yell if capacity is already full!
 | 
						|
 | 
						|
  // Tree is empty
 | 
						|
  if (this.size === 0) {
 | 
						|
    this.keys[0] = key;
 | 
						|
    this.values[0] = value;
 | 
						|
 | 
						|
    this.size++;
 | 
						|
 | 
						|
    this.root = -1;
 | 
						|
 | 
						|
    return this;
 | 
						|
  }
 | 
						|
 | 
						|
  // Walk state
 | 
						|
  var pointer = this.root,
 | 
						|
      newPointer,
 | 
						|
      leftOrRight,
 | 
						|
      opposite,
 | 
						|
      ancestors = [],
 | 
						|
      path = [],
 | 
						|
      ancestor,
 | 
						|
      parent,
 | 
						|
      child,
 | 
						|
      critbit,
 | 
						|
      internal,
 | 
						|
      best,
 | 
						|
      dir,
 | 
						|
      i,
 | 
						|
      l;
 | 
						|
 | 
						|
  // Walking the tree
 | 
						|
  while (true) {
 | 
						|
 | 
						|
    // Traversing an internal node
 | 
						|
    if (pointer > 0) {
 | 
						|
      pointer -= 1;
 | 
						|
 | 
						|
      // Choosing the correct direction
 | 
						|
      dir = getDirection(key, this.critbits[pointer]);
 | 
						|
 | 
						|
      leftOrRight = dir === 0 ? this.lefts : this.rights;
 | 
						|
      newPointer = leftOrRight[pointer];
 | 
						|
 | 
						|
      if (newPointer === 0) {
 | 
						|
 | 
						|
        // Creating a fitting external node
 | 
						|
        pointer = this.size++;
 | 
						|
        leftOrRight[newPointer] = -(pointer + 1);
 | 
						|
        this.keys[pointer] = key;
 | 
						|
        this.values[pointer] = value;
 | 
						|
        return this;
 | 
						|
      }
 | 
						|
 | 
						|
      ancestors.push(pointer);
 | 
						|
      path.push(dir);
 | 
						|
      pointer = newPointer;
 | 
						|
    }
 | 
						|
 | 
						|
    // Reaching an external node
 | 
						|
    else {
 | 
						|
      pointer = -pointer;
 | 
						|
      pointer -= 1;
 | 
						|
 | 
						|
      // 1. Creating a new external node
 | 
						|
      critbit = findCriticalBit(key, this.keys[pointer]);
 | 
						|
 | 
						|
      // Key is identical, we just replace the value
 | 
						|
      if (critbit === -1) {
 | 
						|
        this.values[pointer] = value;
 | 
						|
        return this;
 | 
						|
      }
 | 
						|
 | 
						|
      internal = this.offset++;
 | 
						|
      newPointer = this.size++;
 | 
						|
 | 
						|
      this.keys[newPointer] = key;
 | 
						|
      this.values[newPointer] = value;
 | 
						|
 | 
						|
      this.critbits[internal] = critbit;
 | 
						|
 | 
						|
      dir = getDirection(key, critbit);
 | 
						|
      leftOrRight = dir === 0 ? this.lefts : this.rights;
 | 
						|
      opposite = dir === 0 ? this.rights : this.lefts;
 | 
						|
 | 
						|
      leftOrRight[internal] = -(newPointer + 1);
 | 
						|
      opposite[internal] = -(pointer + 1);
 | 
						|
 | 
						|
      // 2. Bubbling up
 | 
						|
      best = -1;
 | 
						|
      l = ancestors.length;
 | 
						|
 | 
						|
      for (i = l - 1; i >= 0; i--) {
 | 
						|
        ancestor = ancestors[i];
 | 
						|
 | 
						|
        // TODO: this can be made faster
 | 
						|
        if ((this.critbits[ancestor] >> 8) > (critbit >> 8)) {
 | 
						|
          continue;
 | 
						|
        }
 | 
						|
        else if ((this.critbits[ancestor] >> 8) === (critbit >> 8)) {
 | 
						|
          if ((this.critbits[ancestor] & 0xff) < (critbit & 0xff))
 | 
						|
            continue;
 | 
						|
        }
 | 
						|
 | 
						|
        best = i;
 | 
						|
        break;
 | 
						|
      }
 | 
						|
 | 
						|
      // Do we need to attach to the root?
 | 
						|
      if (best < 0) {
 | 
						|
        this.root = internal + 1;
 | 
						|
 | 
						|
        // Need to rewire parent as child?
 | 
						|
        if (l > 0) {
 | 
						|
          parent = ancestors[0];
 | 
						|
 | 
						|
          opposite[internal] = parent + 1;
 | 
						|
        }
 | 
						|
      }
 | 
						|
 | 
						|
      // Simple case without rotation
 | 
						|
      else if (best === l - 1) {
 | 
						|
        parent = ancestors[best];
 | 
						|
        dir = path[best];
 | 
						|
 | 
						|
        leftOrRight = dir === 0 ? this.lefts : this.rights;
 | 
						|
 | 
						|
        leftOrRight[parent] = internal + 1;
 | 
						|
      }
 | 
						|
 | 
						|
      // Full rotation
 | 
						|
      else {
 | 
						|
        parent = ancestors[best];
 | 
						|
        dir = path[best];
 | 
						|
        child = ancestors[best + 1];
 | 
						|
 | 
						|
        opposite[internal] = child + 1;
 | 
						|
 | 
						|
        leftOrRight = dir === 0 ? this.lefts : this.rights;
 | 
						|
 | 
						|
        leftOrRight[parent] = internal + 1;
 | 
						|
      }
 | 
						|
 | 
						|
      return this;
 | 
						|
    }
 | 
						|
  }
 | 
						|
};
 | 
						|
 | 
						|
/**
 | 
						|
 * Method used to get the value attached to the given key in the tree or
 | 
						|
 * undefined if not found.
 | 
						|
 *
 | 
						|
 * @param  {string} key   - Key to get.
 | 
						|
 * @return {any}
 | 
						|
 */
 | 
						|
FixedCritBitTreeMap.prototype.get = function(key) {
 | 
						|
 | 
						|
  // Walk state
 | 
						|
  var pointer = this.root,
 | 
						|
      dir;
 | 
						|
 | 
						|
  // Walking the tree
 | 
						|
  while (true) {
 | 
						|
 | 
						|
    // Dead end
 | 
						|
    if (pointer === 0)
 | 
						|
      return;
 | 
						|
 | 
						|
    // Traversing an internal node
 | 
						|
    if (pointer > 0) {
 | 
						|
      pointer -= 1;
 | 
						|
      dir = getDirection(key, this.critbits[pointer]);
 | 
						|
 | 
						|
      pointer = dir === 0 ? this.lefts[pointer] : this.rights[pointer];
 | 
						|
    }
 | 
						|
 | 
						|
    // Reaching an external node
 | 
						|
    else {
 | 
						|
      pointer = -pointer;
 | 
						|
      pointer -= 1;
 | 
						|
 | 
						|
      if (this.keys[pointer] !== key)
 | 
						|
        return;
 | 
						|
 | 
						|
      return this.values[pointer];
 | 
						|
    }
 | 
						|
  }
 | 
						|
};
 | 
						|
 | 
						|
/**
 | 
						|
 * Method used to return whether the given key exists in the tree.
 | 
						|
 *
 | 
						|
 * @param  {string} key - Key to test.
 | 
						|
 * @return {boolean}
 | 
						|
 */
 | 
						|
FixedCritBitTreeMap.prototype.has = function(key) {
 | 
						|
 | 
						|
  // Walk state
 | 
						|
  var pointer = this.root,
 | 
						|
      dir;
 | 
						|
 | 
						|
  // Walking the tree
 | 
						|
  while (true) {
 | 
						|
 | 
						|
    // Dead end
 | 
						|
    if (pointer === 0)
 | 
						|
      return false;
 | 
						|
 | 
						|
    // Traversing an internal node
 | 
						|
    if (pointer > 0) {
 | 
						|
      pointer -= 1;
 | 
						|
      dir = getDirection(key, this.critbits[pointer]);
 | 
						|
 | 
						|
      pointer = dir === 0 ? this.lefts[pointer] : this.rights[pointer];
 | 
						|
    }
 | 
						|
 | 
						|
    // Reaching an external node
 | 
						|
    else {
 | 
						|
      pointer = -pointer;
 | 
						|
      pointer -= 1;
 | 
						|
 | 
						|
      return this.keys[pointer] === key;
 | 
						|
    }
 | 
						|
  }
 | 
						|
};
 | 
						|
 | 
						|
/**
 | 
						|
 * Method used to iterate over the tree in key order.
 | 
						|
 *
 | 
						|
 * @param  {function}  callback - Function to call for each item.
 | 
						|
 * @param  {object}    scope    - Optional scope.
 | 
						|
 * @return {undefined}
 | 
						|
 */
 | 
						|
FixedCritBitTreeMap.prototype.forEach = function(callback, scope) {
 | 
						|
  scope = arguments.length > 1 ? scope : this;
 | 
						|
 | 
						|
  // Inorder traversal of the tree
 | 
						|
  var current = this.root,
 | 
						|
      stack = [],
 | 
						|
      p;
 | 
						|
 | 
						|
  while (true) {
 | 
						|
 | 
						|
    if (current !== 0) {
 | 
						|
      stack.push(current);
 | 
						|
 | 
						|
      current = current > 0 ? this.lefts[current - 1] : 0;
 | 
						|
    }
 | 
						|
 | 
						|
    else {
 | 
						|
      if (stack.length > 0) {
 | 
						|
        current = stack.pop();
 | 
						|
 | 
						|
        if (current < 0) {
 | 
						|
          p = -current;
 | 
						|
          p -= 1;
 | 
						|
 | 
						|
          callback.call(scope, this.values[p], this.keys[p]);
 | 
						|
        }
 | 
						|
 | 
						|
        current = current > 0 ? this.rights[current - 1] : 0;
 | 
						|
      }
 | 
						|
      else {
 | 
						|
        break;
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
};
 | 
						|
 | 
						|
/**
 | 
						|
 * Convenience known methods.
 | 
						|
 */
 | 
						|
FixedCritBitTreeMap.prototype.inspect = function() {
 | 
						|
  return this;
 | 
						|
};
 | 
						|
 | 
						|
if (typeof Symbol !== 'undefined')
 | 
						|
  FixedCritBitTreeMap.prototype[Symbol.for('nodejs.util.inspect.custom')] = FixedCritBitTreeMap.prototype.inspect;
 | 
						|
 | 
						|
/**
 | 
						|
 * Static @.from function taking an arbitrary iterable & converting it into
 | 
						|
 * a FixedCritBitTreeMap.
 | 
						|
 *
 | 
						|
 * @param  {Iterable} iterable - Target iterable.
 | 
						|
 * @return {FixedCritBitTreeMap}
 | 
						|
 */
 | 
						|
// FixedCritBitTreeMap.from = function(iterable) {
 | 
						|
 | 
						|
// };
 | 
						|
 | 
						|
/**
 | 
						|
 * Exporting.
 | 
						|
 */
 | 
						|
module.exports = FixedCritBitTreeMap;
 |