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.
		
		
		
		
		
			
		
			
				
					516 lines
				
				10 KiB
			
		
		
			
		
	
	
					516 lines
				
				10 KiB
			| 
											3 years ago
										 | /* eslint no-constant-condition: 0 */ | ||
|  | /** | ||
|  |  * Mnemonist CritBitTreeMap | ||
|  |  * ========================= | ||
|  |  * | ||
|  |  * JavaScript implementation of a crit-bit tree, also called PATRICIA tree. | ||
|  |  * This tree is a basically a bitwise radix tree and is supposedly much more | ||
|  |  * efficient than a standard Trie. | ||
|  |  * | ||
|  |  * [References]: | ||
|  |  * https://cr.yp.to/critbit.html
 | ||
|  |  * https://www.imperialviolet.org/binary/critbit.pdf
 | ||
|  |  */ | ||
|  | var bitwise = require('./utils/bitwise.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 (1 + (byte | mask)) >> 8; | ||
|  | } | ||
|  | 
 | ||
|  | /** | ||
|  |  * 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.criticalBit8Mask( | ||
|  |         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.criticalBit8Mask(b.charCodeAt(i)); | ||
|  | 
 | ||
|  |   return (i << 8) | mask; | ||
|  | } | ||
|  | 
 | ||
|  | /** | ||
|  |  * Class representing a crit-bit tree's internal node. | ||
|  |  * | ||
|  |  * @constructor | ||
|  |  * @param {number} critbit - Packed address of byte + mask. | ||
|  |  */ | ||
|  | function InternalNode(critbit) { | ||
|  |   this.critbit = critbit; | ||
|  |   this.left = null; | ||
|  |   this.right = null; | ||
|  | } | ||
|  | 
 | ||
|  | /** | ||
|  |  * Class representing a crit-bit tree's external node. | ||
|  |  * Note that it is possible to replace those nodes by flat arrays. | ||
|  |  * | ||
|  |  * @constructor | ||
|  |  * @param {string} key   - Node's key. | ||
|  |  * @param {any}    value - Arbitrary value. | ||
|  |  */ | ||
|  | function ExternalNode(key, value) { | ||
|  |   this.key = key; | ||
|  |   this.value = value; | ||
|  | } | ||
|  | 
 | ||
|  | /** | ||
|  |  * CritBitTreeMap. | ||
|  |  * | ||
|  |  * @constructor | ||
|  |  */ | ||
|  | function CritBitTreeMap() { | ||
|  | 
 | ||
|  |   // Properties
 | ||
|  |   this.root = null; | ||
|  |   this.size = 0; | ||
|  | 
 | ||
|  |   this.clear(); | ||
|  | } | ||
|  | 
 | ||
|  | /** | ||
|  |  * Method used to clear the CritBitTreeMap. | ||
|  |  * | ||
|  |  * @return {undefined} | ||
|  |  */ | ||
|  | CritBitTreeMap.prototype.clear = function() { | ||
|  | 
 | ||
|  |   // Properties
 | ||
|  |   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 {CritBitTreeMap} | ||
|  |  */ | ||
|  | CritBitTreeMap.prototype.set = function(key, value) { | ||
|  | 
 | ||
|  |   // Tree is empty
 | ||
|  |   if (this.size === 0) { | ||
|  |     this.root = new ExternalNode(key, value); | ||
|  |     this.size++; | ||
|  | 
 | ||
|  |     return this; | ||
|  |   } | ||
|  | 
 | ||
|  |   // Walk state
 | ||
|  |   var node = this.root, | ||
|  |       ancestors = [], | ||
|  |       path = [], | ||
|  |       ancestor, | ||
|  |       parent, | ||
|  |       child, | ||
|  |       critbit, | ||
|  |       internal, | ||
|  |       left, | ||
|  |       leftPath, | ||
|  |       best, | ||
|  |       dir, | ||
|  |       i, | ||
|  |       l; | ||
|  | 
 | ||
|  |   // Walking the tree
 | ||
|  |   while (true) { | ||
|  | 
 | ||
|  |     // Traversing an internal node
 | ||
|  |     if (node instanceof InternalNode) { | ||
|  |       dir = getDirection(key, node.critbit); | ||
|  | 
 | ||
|  |       // Going left & creating key if not yet there
 | ||
|  |       if (dir === 0) { | ||
|  |         if (!node.left) { | ||
|  |           node.left = new ExternalNode(key, value); | ||
|  |           return this; | ||
|  |         } | ||
|  | 
 | ||
|  |         ancestors.push(node); | ||
|  |         path.push(true); | ||
|  | 
 | ||
|  |         node = node.left; | ||
|  |       } | ||
|  | 
 | ||
|  |       // Going right & creating key if not yet there
 | ||
|  |       else { | ||
|  |         if (!node.right) { | ||
|  |           node.right = new ExternalNode(key, value); | ||
|  |           return this; | ||
|  |         } | ||
|  | 
 | ||
|  |         ancestors.push(node); | ||
|  |         path.push(false); | ||
|  | 
 | ||
|  |         node = node.right; | ||
|  |       } | ||
|  |     } | ||
|  | 
 | ||
|  |     // Reaching an external node
 | ||
|  |     else { | ||
|  | 
 | ||
|  |       // 1. Creating a new external node
 | ||
|  |       critbit = findCriticalBit(key, node.key); | ||
|  | 
 | ||
|  |       // Key is identical, we just replace the value
 | ||
|  |       if (critbit === -1) { | ||
|  |         node.value = value; | ||
|  |         return this; | ||
|  |       } | ||
|  | 
 | ||
|  |       this.size++; | ||
|  | 
 | ||
|  |       internal = new InternalNode(critbit); | ||
|  | 
 | ||
|  |       left = getDirection(key, critbit) === 0; | ||
|  | 
 | ||
|  |       // TODO: maybe setting opposite pointer is not necessary
 | ||
|  |       if (left) { | ||
|  |         internal.left = new ExternalNode(key, value); | ||
|  |         internal.right = node; | ||
|  |       } | ||
|  |       else { | ||
|  |         internal.left = node; | ||
|  |         internal.right = new ExternalNode(key, value); | ||
|  |       } | ||
|  | 
 | ||
|  |       // 2. Bubbling up
 | ||
|  |       best = -1; | ||
|  |       l = ancestors.length; | ||
|  | 
 | ||
|  |       for (i = l - 1; i >= 0; i--) { | ||
|  |         ancestor = ancestors[i]; | ||
|  | 
 | ||
|  |         if (ancestor.critbit > critbit) | ||
|  |           continue; | ||
|  | 
 | ||
|  |         best = i; | ||
|  |         break; | ||
|  |       } | ||
|  | 
 | ||
|  |       // Do we need to attach to the root?
 | ||
|  |       if (best < 0) { | ||
|  |         this.root = internal; | ||
|  | 
 | ||
|  |         // Need to rewire parent as child?
 | ||
|  |         if (l > 0) { | ||
|  |           parent = ancestors[0]; | ||
|  | 
 | ||
|  |           if (left) | ||
|  |             internal.right = parent; | ||
|  |           else | ||
|  |             internal.left = parent; | ||
|  |         } | ||
|  |       } | ||
|  | 
 | ||
|  |       // Simple case without rotation
 | ||
|  |       else if (best === l - 1) { | ||
|  |         parent = ancestors[best]; | ||
|  |         leftPath = path[best]; | ||
|  | 
 | ||
|  |         if (leftPath) | ||
|  |           parent.left = internal; | ||
|  |         else | ||
|  |           parent.right = internal; | ||
|  |       } | ||
|  | 
 | ||
|  |       // Full rotation
 | ||
|  |       else { | ||
|  |         parent = ancestors[best]; | ||
|  |         leftPath = path[best]; | ||
|  |         child = ancestors[best + 1]; | ||
|  | 
 | ||
|  |         if (leftPath) | ||
|  |           parent.left = internal; | ||
|  |         else | ||
|  |           parent.right = internal; | ||
|  | 
 | ||
|  |         if (left) | ||
|  |           internal.right = child; | ||
|  |         else | ||
|  |           internal.left = child; | ||
|  |       } | ||
|  | 
 | ||
|  |       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} | ||
|  |  */ | ||
|  | CritBitTreeMap.prototype.get = function(key) { | ||
|  | 
 | ||
|  |   // Walk state
 | ||
|  |   var node = this.root, | ||
|  |       dir; | ||
|  | 
 | ||
|  |   // Walking the tree
 | ||
|  |   while (true) { | ||
|  | 
 | ||
|  |     // Dead end
 | ||
|  |     if (node === null) | ||
|  |       return; | ||
|  | 
 | ||
|  |     // Traversing an internal node
 | ||
|  |     if (node instanceof InternalNode) { | ||
|  |       dir = getDirection(key, node.critbit); | ||
|  | 
 | ||
|  |       node = dir ? node.right : node.left; | ||
|  |     } | ||
|  | 
 | ||
|  |     // Reaching an external node
 | ||
|  |     else { | ||
|  |       if (node.key !== key) | ||
|  |         return; | ||
|  | 
 | ||
|  |       return node.value; | ||
|  |     } | ||
|  |   } | ||
|  | }; | ||
|  | 
 | ||
|  | /** | ||
|  |  * Method used to return whether the given key exists in the tree. | ||
|  |  * | ||
|  |  * @param  {string} key - Key to test. | ||
|  |  * @return {boolean} | ||
|  |  */ | ||
|  | CritBitTreeMap.prototype.has = function(key) { | ||
|  | 
 | ||
|  |   // Walk state
 | ||
|  |   var node = this.root, | ||
|  |       dir; | ||
|  | 
 | ||
|  |   // Walking the tree
 | ||
|  |   while (true) { | ||
|  | 
 | ||
|  |     // Dead end
 | ||
|  |     if (node === null) | ||
|  |       return false; | ||
|  | 
 | ||
|  |     // Traversing an internal node
 | ||
|  |     if (node instanceof InternalNode) { | ||
|  |       dir = getDirection(key, node.critbit); | ||
|  | 
 | ||
|  |       node = dir ? node.right : node.left; | ||
|  |     } | ||
|  | 
 | ||
|  |     // Reaching an external node
 | ||
|  |     else { | ||
|  |       return node.key === key; | ||
|  |     } | ||
|  |   } | ||
|  | }; | ||
|  | 
 | ||
|  | /** | ||
|  |  * Method used to delete the given key from the tree and return whether the | ||
|  |  * key did exist or not. | ||
|  |  * | ||
|  |  * @param  {string} key - Key to delete. | ||
|  |  * @return {boolean} | ||
|  |  */ | ||
|  | CritBitTreeMap.prototype.delete = function(key) { | ||
|  | 
 | ||
|  |   // Walk state
 | ||
|  |   var node = this.root, | ||
|  |       dir; | ||
|  | 
 | ||
|  |   var parent = null, | ||
|  |       grandParent = null, | ||
|  |       wentLeftForParent = false, | ||
|  |       wentLeftForGrandparent = false; | ||
|  | 
 | ||
|  |   // Walking the tree
 | ||
|  |   while (true) { | ||
|  | 
 | ||
|  |     // Dead end
 | ||
|  |     if (node === null) | ||
|  |       return false; | ||
|  | 
 | ||
|  |     // Traversing an internal node
 | ||
|  |     if (node instanceof InternalNode) { | ||
|  |       dir = getDirection(key, node.critbit); | ||
|  | 
 | ||
|  |       if (dir === 0) { | ||
|  |         grandParent = parent; | ||
|  |         wentLeftForGrandparent = wentLeftForParent; | ||
|  |         parent = node; | ||
|  |         wentLeftForParent = true; | ||
|  | 
 | ||
|  |         node = node.left; | ||
|  |       } | ||
|  |       else { | ||
|  |         grandParent = parent; | ||
|  |         wentLeftForGrandparent = wentLeftForParent; | ||
|  |         parent = node; | ||
|  |         wentLeftForParent = false; | ||
|  | 
 | ||
|  |         node = node.right; | ||
|  |       } | ||
|  |     } | ||
|  | 
 | ||
|  |     // Reaching an external node
 | ||
|  |     else { | ||
|  |       if (key !== node.key) | ||
|  |         return false; | ||
|  | 
 | ||
|  |       this.size--; | ||
|  | 
 | ||
|  |       // Rewiring
 | ||
|  |       if (parent === null) { | ||
|  |         this.root = null; | ||
|  |       } | ||
|  | 
 | ||
|  |       else if (grandParent === null) { | ||
|  |         if (wentLeftForParent) | ||
|  |           this.root = parent.right; | ||
|  |         else | ||
|  |           this.root = parent.left; | ||
|  |       } | ||
|  | 
 | ||
|  |       else { | ||
|  |         if (wentLeftForGrandparent) { | ||
|  |           if (wentLeftForParent) { | ||
|  |             grandParent.left = parent.right; | ||
|  |           } | ||
|  |           else { | ||
|  |             grandParent.left = parent.left; | ||
|  |           } | ||
|  |         } | ||
|  |         else { | ||
|  |           if (wentLeftForParent) { | ||
|  |             grandParent.right = parent.right; | ||
|  |           } | ||
|  |           else { | ||
|  |             grandParent.right = parent.left; | ||
|  |           } | ||
|  |         } | ||
|  |       } | ||
|  | 
 | ||
|  |       return true; | ||
|  |     } | ||
|  |   } | ||
|  | }; | ||
|  | 
 | ||
|  | /** | ||
|  |  * 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} | ||
|  |  */ | ||
|  | CritBitTreeMap.prototype.forEach = function(callback, scope) { | ||
|  |   scope = arguments.length > 1 ? scope : this; | ||
|  | 
 | ||
|  |   // Inorder traversal of the tree
 | ||
|  |   var current = this.root, | ||
|  |       stack = []; | ||
|  | 
 | ||
|  |   while (true) { | ||
|  | 
 | ||
|  |     if (current !== null) { | ||
|  |       stack.push(current); | ||
|  | 
 | ||
|  |       current = current instanceof InternalNode ? current.left : null; | ||
|  |     } | ||
|  | 
 | ||
|  |     else { | ||
|  |       if (stack.length > 0) { | ||
|  |         current = stack.pop(); | ||
|  | 
 | ||
|  |         if (current instanceof ExternalNode) | ||
|  |           callback.call(scope, current.value, current.key); | ||
|  | 
 | ||
|  |         current = current instanceof InternalNode ? current.right : null; | ||
|  |       } | ||
|  |       else { | ||
|  |         break; | ||
|  |       } | ||
|  |     } | ||
|  |   } | ||
|  | }; | ||
|  | 
 | ||
|  | /** | ||
|  |  * Convenience known methods. | ||
|  |  */ | ||
|  | CritBitTreeMap.prototype.inspect = function() { | ||
|  |   return this; | ||
|  | }; | ||
|  | 
 | ||
|  | if (typeof Symbol !== 'undefined') | ||
|  |   CritBitTreeMap.prototype[Symbol.for('nodejs.util.inspect.custom')] = CritBitTreeMap.prototype.inspect; | ||
|  | 
 | ||
|  | /** | ||
|  |  * Static @.from function taking an arbitrary iterable & converting it into | ||
|  |  * a CritBitTreeMap. | ||
|  |  * | ||
|  |  * @param  {Iterable} iterable - Target iterable. | ||
|  |  * @return {CritBitTreeMap} | ||
|  |  */ | ||
|  | // CritBitTreeMap.from = function(iterable) {
 | ||
|  | 
 | ||
|  | // };
 | ||
|  | 
 | ||
|  | /** | ||
|  |  * Exporting. | ||
|  |  */ | ||
|  | module.exports = CritBitTreeMap; |