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.
		
		
		
		
		
			
		
			
				
					210 lines
				
				4.8 KiB
			
		
		
			
		
	
	
					210 lines
				
				4.8 KiB
			| 
											3 years ago
										 | /** | ||
|  |  * Mnemonist HashedArrayTree | ||
|  |  * ========================== | ||
|  |  * | ||
|  |  * Abstract implementation of a hashed array tree representing arrays growing | ||
|  |  * dynamically. | ||
|  |  */ | ||
|  | 
 | ||
|  | /** | ||
|  |  * Defaults. | ||
|  |  */ | ||
|  | var DEFAULT_BLOCK_SIZE = 1024; | ||
|  | 
 | ||
|  | /** | ||
|  |  * Helpers. | ||
|  |  */ | ||
|  | function powerOfTwo(x) { | ||
|  |   return (x & (x - 1)) === 0; | ||
|  | } | ||
|  | 
 | ||
|  | /** | ||
|  |  * HashedArrayTree. | ||
|  |  * | ||
|  |  * @constructor | ||
|  |  * @param {function}      ArrayClass           - An array constructor. | ||
|  |  * @param {number|object} initialCapacityOrOptions - Self-explanatory. | ||
|  |  */ | ||
|  | function HashedArrayTree(ArrayClass, initialCapacityOrOptions) { | ||
|  |   if (arguments.length < 1) | ||
|  |     throw new Error('mnemonist/hashed-array-tree: expecting at least a byte array constructor.'); | ||
|  | 
 | ||
|  |   var initialCapacity = initialCapacityOrOptions || 0, | ||
|  |       blockSize = DEFAULT_BLOCK_SIZE, | ||
|  |       initialLength = 0; | ||
|  | 
 | ||
|  |   if (typeof initialCapacityOrOptions === 'object') { | ||
|  |     initialCapacity = initialCapacityOrOptions.initialCapacity || 0; | ||
|  |     initialLength = initialCapacityOrOptions.initialLength || 0; | ||
|  |     blockSize = initialCapacityOrOptions.blockSize || DEFAULT_BLOCK_SIZE; | ||
|  |   } | ||
|  | 
 | ||
|  |   if (!blockSize || !powerOfTwo(blockSize)) | ||
|  |     throw new Error('mnemonist/hashed-array-tree: block size should be a power of two.'); | ||
|  | 
 | ||
|  |   var capacity = Math.max(initialLength, initialCapacity), | ||
|  |       initialBlocks = Math.ceil(capacity / blockSize); | ||
|  | 
 | ||
|  |   this.ArrayClass = ArrayClass; | ||
|  |   this.length = initialLength; | ||
|  |   this.capacity = initialBlocks * blockSize; | ||
|  |   this.blockSize = blockSize; | ||
|  |   this.offsetMask = blockSize - 1; | ||
|  |   this.blockMask = Math.log2(blockSize); | ||
|  | 
 | ||
|  |   // Allocating initial blocks
 | ||
|  |   this.blocks = new Array(initialBlocks); | ||
|  | 
 | ||
|  |   for (var i = 0; i < initialBlocks; i++) | ||
|  |     this.blocks[i] = new this.ArrayClass(this.blockSize); | ||
|  | } | ||
|  | 
 | ||
|  | /** | ||
|  |  * Method used to set a value. | ||
|  |  * | ||
|  |  * @param  {number} index - Index to edit. | ||
|  |  * @param  {any}    value - Value. | ||
|  |  * @return {HashedArrayTree} | ||
|  |  */ | ||
|  | HashedArrayTree.prototype.set = function(index, value) { | ||
|  | 
 | ||
|  |   // Out of bounds?
 | ||
|  |   if (this.length < index) | ||
|  |     throw new Error('HashedArrayTree(' + this.ArrayClass.name + ').set: index out of bounds.'); | ||
|  | 
 | ||
|  |   var block = index >> this.blockMask, | ||
|  |       i = index & this.offsetMask; | ||
|  | 
 | ||
|  |   this.blocks[block][i] = value; | ||
|  | 
 | ||
|  |   return this; | ||
|  | }; | ||
|  | 
 | ||
|  | /** | ||
|  |  * Method used to get a value. | ||
|  |  * | ||
|  |  * @param  {number} index - Index to retrieve. | ||
|  |  * @return {any} | ||
|  |  */ | ||
|  | HashedArrayTree.prototype.get = function(index) { | ||
|  |   if (this.length < index) | ||
|  |     return; | ||
|  | 
 | ||
|  |   var block = index >> this.blockMask, | ||
|  |       i = index & this.offsetMask; | ||
|  | 
 | ||
|  |   return this.blocks[block][i]; | ||
|  | }; | ||
|  | 
 | ||
|  | /** | ||
|  |  * Method used to grow the array. | ||
|  |  * | ||
|  |  * @param  {number}          capacity - Optional capacity to accomodate. | ||
|  |  * @return {HashedArrayTree} | ||
|  |  */ | ||
|  | HashedArrayTree.prototype.grow = function(capacity) { | ||
|  |   if (typeof capacity !== 'number') | ||
|  |     capacity = this.capacity + this.blockSize; | ||
|  | 
 | ||
|  |   if (this.capacity >= capacity) | ||
|  |     return this; | ||
|  | 
 | ||
|  |   while (this.capacity < capacity) { | ||
|  |     this.blocks.push(new this.ArrayClass(this.blockSize)); | ||
|  |     this.capacity += this.blockSize; | ||
|  |   } | ||
|  | 
 | ||
|  |   return this; | ||
|  | }; | ||
|  | 
 | ||
|  | /** | ||
|  |  * Method used to resize the array. Won't deallocate. | ||
|  |  * | ||
|  |  * @param  {number}       length - Target length. | ||
|  |  * @return {HashedArrayTree} | ||
|  |  */ | ||
|  | HashedArrayTree.prototype.resize = function(length) { | ||
|  |   if (length === this.length) | ||
|  |     return this; | ||
|  | 
 | ||
|  |   if (length < this.length) { | ||
|  |     this.length = length; | ||
|  |     return this; | ||
|  |   } | ||
|  | 
 | ||
|  |   this.length = length; | ||
|  |   this.grow(length); | ||
|  | 
 | ||
|  |   return this; | ||
|  | }; | ||
|  | 
 | ||
|  | /** | ||
|  |  * Method used to push a value into the array. | ||
|  |  * | ||
|  |  * @param  {any}    value - Value to push. | ||
|  |  * @return {number}       - Length of the array. | ||
|  |  */ | ||
|  | HashedArrayTree.prototype.push = function(value) { | ||
|  |   if (this.capacity === this.length) | ||
|  |     this.grow(); | ||
|  | 
 | ||
|  |   var index = this.length; | ||
|  | 
 | ||
|  |   var block = index >> this.blockMask, | ||
|  |       i = index & this.offsetMask; | ||
|  | 
 | ||
|  |   this.blocks[block][i] = value; | ||
|  | 
 | ||
|  |   return ++this.length; | ||
|  | }; | ||
|  | 
 | ||
|  | /** | ||
|  |  * Method used to pop the last value of the array. | ||
|  |  * | ||
|  |  * @return {number} - The popped value. | ||
|  |  */ | ||
|  | HashedArrayTree.prototype.pop = function() { | ||
|  |   if (this.length === 0) | ||
|  |     return; | ||
|  | 
 | ||
|  |   var lastBlock = this.blocks[this.blocks.length - 1]; | ||
|  | 
 | ||
|  |   var i = (--this.length) & this.offsetMask; | ||
|  | 
 | ||
|  |   return lastBlock[i]; | ||
|  | }; | ||
|  | 
 | ||
|  | /** | ||
|  |  * Convenience known methods. | ||
|  |  */ | ||
|  | HashedArrayTree.prototype.inspect = function() { | ||
|  |   var proxy = new this.ArrayClass(this.length), | ||
|  |       block; | ||
|  | 
 | ||
|  |   for (var i = 0, l = this.length; i < l; i++) { | ||
|  |     block = i >> this.blockMask; | ||
|  |     proxy[i] = this.blocks[block][i & this.offsetMask]; | ||
|  |   } | ||
|  | 
 | ||
|  |   proxy.type = this.ArrayClass.name; | ||
|  |   proxy.items = this.length; | ||
|  |   proxy.capacity = this.capacity; | ||
|  |   proxy.blockSize = this.blockSize; | ||
|  | 
 | ||
|  |   // Trick so that node displays the name of the constructor
 | ||
|  |   Object.defineProperty(proxy, 'constructor', { | ||
|  |     value: HashedArrayTree, | ||
|  |     enumerable: false | ||
|  |   }); | ||
|  | 
 | ||
|  |   return proxy; | ||
|  | }; | ||
|  | 
 | ||
|  | if (typeof Symbol !== 'undefined') | ||
|  |   HashedArrayTree.prototype[Symbol.for('nodejs.util.inspect.custom')] = HashedArrayTree.prototype.inspect; | ||
|  | 
 | ||
|  | /** | ||
|  |  * Exporting. | ||
|  |  */ | ||
|  | module.exports = HashedArrayTree; |