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