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