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