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.
		
		
		
		
		
			
		
			
				
					252 lines
				
				4.9 KiB
			
		
		
			
		
	
	
					252 lines
				
				4.9 KiB
			| 
								 
											3 years ago
										 
									 | 
							
								/* eslint no-constant-condition: 0 */
							 | 
						||
| 
								 | 
							
								/**
							 | 
						||
| 
								 | 
							
								 * Mnemonist SemiDynamicTrie
							 | 
						||
| 
								 | 
							
								 * ==========================
							 | 
						||
| 
								 | 
							
								 *
							 | 
						||
| 
								 | 
							
								 * Lowlevel Trie working at character level, storing information in typed
							 | 
						||
| 
								 | 
							
								 * array and organizing its children in linked lists.
							 | 
						||
| 
								 | 
							
								 *
							 | 
						||
| 
								 | 
							
								 * This implementation also uses a "fat node" strategy to boost access to some
							 | 
						||
| 
								 | 
							
								 * bloated node's children when the number of children rises above a certain
							 | 
						||
| 
								 | 
							
								 * threshold.
							 | 
						||
| 
								 | 
							
								 */
							 | 
						||
| 
								 | 
							
								var Vector = require('./vector.js');
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// TODO: rename => ternary search tree
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								/**
							 | 
						||
| 
								 | 
							
								 * Constants.
							 | 
						||
| 
								 | 
							
								 */
							 | 
						||
| 
								 | 
							
								const MAX_LINKED = 7;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								/**
							 | 
						||
| 
								 | 
							
								 * SemiDynamicTrie.
							 | 
						||
| 
								 | 
							
								 *
							 | 
						||
| 
								 | 
							
								 * @constructor
							 | 
						||
| 
								 | 
							
								 */
							 | 
						||
| 
								 | 
							
								function SemiDynamicTrie() {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  // Properties
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  // TODO: make it 16 bits
							 | 
						||
| 
								 | 
							
								  this.characters = new Vector.Uint8Vector(256);
							 | 
						||
| 
								 | 
							
								  this.nextPointers = new Vector.Int32Vector(256);
							 | 
						||
| 
								 | 
							
								  this.childPointers = new Vector.Uint32Vector(256);
							 | 
						||
| 
								 | 
							
								  this.maps = new Vector.Uint32Vector(256);
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								/**
							 | 
						||
| 
								 | 
							
								 * Method used to clear the structure.
							 | 
						||
| 
								 | 
							
								 *
							 | 
						||
| 
								 | 
							
								 * @return {undefined}
							 | 
						||
| 
								 | 
							
								 */
							 | 
						||
| 
								 | 
							
								SemiDynamicTrie.prototype.clear = function() {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  // Properties
							 | 
						||
| 
								 | 
							
								};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								SemiDynamicTrie.prototype.ensureSibling = function(block, character) {
							 | 
						||
| 
								 | 
							
								  var nextCharacter,
							 | 
						||
| 
								 | 
							
								      nextBlock,
							 | 
						||
| 
								 | 
							
								      newBlock;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  // Do we have a root?
							 | 
						||
| 
								 | 
							
								  if (this.characters.length === 0) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    this.nextPointers.push(0);
							 | 
						||
| 
								 | 
							
								    this.childPointers.push(0);
							 | 
						||
| 
								 | 
							
								    this.characters.push(character);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    return block;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  // Are we traversing a fat node?
							 | 
						||
| 
								 | 
							
								  var fatNode = this.nextPointers.array[block];
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  if (fatNode < 0) {
							 | 
						||
| 
								 | 
							
								    var mapIndex = -fatNode + character;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    nextBlock = this.maps.array[mapIndex];
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (nextBlock !== 0)
							 | 
						||
| 
								 | 
							
								      return nextBlock;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    newBlock = this.characters.length;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    this.nextPointers.push(0);
							 | 
						||
| 
								 | 
							
								    this.childPointers.push(0);
							 | 
						||
| 
								 | 
							
								    this.characters.push(character);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    this.maps.set(mapIndex, newBlock);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    return newBlock;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  var listLength = 1,
							 | 
						||
| 
								 | 
							
								      startingBlock = block;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  while (true) {
							 | 
						||
| 
								 | 
							
								    nextCharacter = this.characters.array[block];
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (nextCharacter === character)
							 | 
						||
| 
								 | 
							
								      return block;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    nextBlock = this.nextPointers.array[block];
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (nextBlock === 0)
							 | 
						||
| 
								 | 
							
								      break;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    listLength++;
							 | 
						||
| 
								 | 
							
								    block = nextBlock;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  // If the list is too long, we create a fat node
							 | 
						||
| 
								 | 
							
								  if (listLength > MAX_LINKED) {
							 | 
						||
| 
								 | 
							
								    block = startingBlock;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    var offset = this.maps.length;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    this.maps.resize(offset + 255);
							 | 
						||
| 
								 | 
							
								    this.maps.set(offset + 255, 0);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    while (true) {
							 | 
						||
| 
								 | 
							
								      nextBlock = this.nextPointers.array[block];
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      if (nextBlock === 0)
							 | 
						||
| 
								 | 
							
								        break;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      nextCharacter = this.characters.array[nextBlock];
							 | 
						||
| 
								 | 
							
								      this.maps.set(offset + nextCharacter, nextBlock);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      block = nextBlock;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    this.nextPointers.set(startingBlock, -offset);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    newBlock = this.characters.length;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    this.nextPointers.push(0);
							 | 
						||
| 
								 | 
							
								    this.childPointers.push(0);
							 | 
						||
| 
								 | 
							
								    this.characters.push(character);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    this.maps.set(offset + character, newBlock);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    return newBlock;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  // Else, we append the character to the list
							 | 
						||
| 
								 | 
							
								  newBlock = this.characters.length;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  this.nextPointers.push(0);
							 | 
						||
| 
								 | 
							
								  this.childPointers.push(0);
							 | 
						||
| 
								 | 
							
								  this.nextPointers.set(block, newBlock);
							 | 
						||
| 
								 | 
							
								  this.characters.push(character);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  return newBlock;
							 | 
						||
| 
								 | 
							
								};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								SemiDynamicTrie.prototype.findSibling = function(block, character) {
							 | 
						||
| 
								 | 
							
								  var nextCharacter;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  // Do we have a fat node?
							 | 
						||
| 
								 | 
							
								  var fatNode = this.nextPointers.array[block];
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  if (fatNode < 0) {
							 | 
						||
| 
								 | 
							
								    var mapIndex = -fatNode + character;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    var nextBlock = this.maps.array[mapIndex];
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (nextBlock === 0)
							 | 
						||
| 
								 | 
							
								      return -1;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    return nextBlock;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  while (true) {
							 | 
						||
| 
								 | 
							
								    nextCharacter = this.characters.array[block];
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (nextCharacter === character)
							 | 
						||
| 
								 | 
							
								      return block;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    block = this.nextPointers.array[block];
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (block === 0)
							 | 
						||
| 
								 | 
							
								      return -1;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								SemiDynamicTrie.prototype.add = function(key) {
							 | 
						||
| 
								 | 
							
								  var keyCharacter,
							 | 
						||
| 
								 | 
							
								      childBlock,
							 | 
						||
| 
								 | 
							
								      block = 0;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  var i = 0, l = key.length;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  // Going as far as possible
							 | 
						||
| 
								 | 
							
								  while (i < l) {
							 | 
						||
| 
								 | 
							
								    keyCharacter = key.charCodeAt(i);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // Ensuring a correct sibling exists
							 | 
						||
| 
								 | 
							
								    block = this.ensureSibling(block, keyCharacter);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    i++;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (i < l) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      // Descending
							 | 
						||
| 
								 | 
							
								      childBlock = this.childPointers.array[block];
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      if (childBlock === 0)
							 | 
						||
| 
								 | 
							
								        break;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      block = childBlock;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  // Adding as many blocks as necessary
							 | 
						||
| 
								 | 
							
								  while (i < l) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    childBlock = this.characters.length;
							 | 
						||
| 
								 | 
							
								    this.characters.push(key.charCodeAt(i));
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    this.childPointers.push(0);
							 | 
						||
| 
								 | 
							
								    this.nextPointers.push(0);
							 | 
						||
| 
								 | 
							
								    this.childPointers.set(block, childBlock);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    block = childBlock;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    i++;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								SemiDynamicTrie.prototype.has = function(key) {
							 | 
						||
| 
								 | 
							
								  var i, l;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  var block = 0,
							 | 
						||
| 
								 | 
							
								      siblingBlock;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  for (i = 0, l = key.length; i < l; i++) {
							 | 
						||
| 
								 | 
							
								    siblingBlock = this.findSibling(block, key.charCodeAt(i));
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (siblingBlock === -1)
							 | 
						||
| 
								 | 
							
								      return false;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // TODO: be sure
							 | 
						||
| 
								 | 
							
								    if (i === l - 1)
							 | 
						||
| 
								 | 
							
								      return true;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    block = this.childPointers.array[siblingBlock];
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (block === 0)
							 | 
						||
| 
								 | 
							
								      return false;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  // TODO: fix, should have a leaf pointer somehow
							 | 
						||
| 
								 | 
							
								  return true;
							 | 
						||
| 
								 | 
							
								};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								/**
							 | 
						||
| 
								 | 
							
								 * Exporting.
							 | 
						||
| 
								 | 
							
								 */
							 | 
						||
| 
								 | 
							
								module.exports = SemiDynamicTrie;
							 |