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