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.
		
		
		
		
		
			
		
			
				
					
					
						
							448 lines
						
					
					
						
							10 KiB
						
					
					
				
			
		
		
	
	
							448 lines
						
					
					
						
							10 KiB
						
					
					
				| /**
 | |
|  * Mnemonist MultiArray
 | |
|  * =====================
 | |
|  *
 | |
|  * Memory-efficient representation of an array of arrays. In JavaScript and
 | |
|  * most high-level languages, creating objects has a cost. This implementation
 | |
|  * is therefore able to represent nested containers without needing to create
 | |
|  * objects. This works by storing singly linked lists in a single flat array.
 | |
|  * However, this means that this structure comes with some read/write
 | |
|  * overhead but consume very few memory.
 | |
|  *
 | |
|  * This structure should be particularly suited to indices that will need to
 | |
|  * merge arrays anyway when queried and that are quite heavily hit (such as
 | |
|  * an inverted index or a quad tree).
 | |
|  *
 | |
|  * Note: the implementation does not require to keep track of head pointers
 | |
|  * but this comes with some advantages such as not needing to offset pointers
 | |
|  * by 1 and being able to perform in-order iteration. This remains quite lean
 | |
|  * in memory and does not hinder performance whatsoever.
 | |
|  */
 | |
| var typed = require('./utils/typed-arrays.js'),
 | |
|     Vector = require('./vector.js'),
 | |
|     Iterator = require('obliterator/iterator');
 | |
| 
 | |
| var PointerVector = Vector.PointerVector;
 | |
| 
 | |
| /**
 | |
|  * MultiArray.
 | |
|  *
 | |
|  * @constructor
 | |
|  */
 | |
| function MultiArray(Container, capacity) {
 | |
|   this.capacity = capacity || null;
 | |
|   this.Container = Container || Array;
 | |
|   this.hasFixedCapacity = this.capacity !== null;
 | |
| 
 | |
|   if (typeof this.Container !== 'function')
 | |
|     throw new Error('mnemonist/multi-array.constructor: container should be a function.');
 | |
| 
 | |
|   this.clear();
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Method used to clear the structure.
 | |
|  *
 | |
|  * @return {undefined}
 | |
|  */
 | |
| MultiArray.prototype.clear = function() {
 | |
| 
 | |
|   // Properties
 | |
|   this.size = 0;
 | |
|   this.dimension = 0;
 | |
| 
 | |
|   // NOTE: #.heads, #.tails & #.lengths have a length equal to the dimension of
 | |
|   // the array, while #.pointers has a length equal to its size.
 | |
| 
 | |
|   // Storage
 | |
|   if (this.hasFixedCapacity) {
 | |
|     var capacity = this.capacity;
 | |
| 
 | |
|     var PointerArray = typed.getPointerArray(capacity);
 | |
| 
 | |
|     var policy = function(currentCapacity) {
 | |
|       var newCapacity = Math.max(1, Math.ceil(currentCapacity * 1.5));
 | |
| 
 | |
|       // Clamping max allocation
 | |
|       return Math.min(newCapacity, capacity);
 | |
|     };
 | |
| 
 | |
|     var initialCapacity = Math.max(8, capacity);
 | |
| 
 | |
|     this.tails = new Vector(PointerArray, {policy: policy, initialCapacity: initialCapacity});
 | |
|     this.lengths = new Vector(PointerArray, {policy: policy, initialCapacity: initialCapacity});
 | |
|     this.pointers = new PointerArray(capacity);
 | |
| 
 | |
|     this.items = new this.Container(capacity);
 | |
|   }
 | |
|   else {
 | |
| 
 | |
|     this.tails = new PointerVector();
 | |
|     this.lengths = new PointerVector();
 | |
|     this.pointers = new PointerVector();
 | |
| 
 | |
|     this.items = new this.Container();
 | |
|   }
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Method used to add an item to the container at the given index.
 | |
|  *
 | |
|  * @param  {number} index - Index of the container.
 | |
|  * @param  {any}    item  - Item to add.
 | |
|  * @return {MultiArray}
 | |
|  */
 | |
| MultiArray.prototype.set = function(index, item) {
 | |
|   var pointer = this.size;
 | |
| 
 | |
|   // TODO: this can be factorized!
 | |
| 
 | |
|   if (this.hasFixedCapacity) {
 | |
| 
 | |
|     if (index >= this.capacity || this.size === this.capacity)
 | |
|       throw new Error('mnemonist/multi-array: attempting to allocate further than capacity.');
 | |
| 
 | |
|     // This linked list does not exist yet. Let's create it
 | |
|     if (index >= this.dimension) {
 | |
| 
 | |
|       // We may be required to grow the vectors
 | |
|       this.dimension = index + 1;
 | |
|       this.tails.grow(this.dimension);
 | |
|       this.lengths.grow(this.dimension);
 | |
| 
 | |
|       this.tails.resize(this.dimension);
 | |
|       this.lengths.resize(this.dimension);
 | |
| 
 | |
|       this.lengths.array[index] = 1;
 | |
|     }
 | |
| 
 | |
|     // Appending to the list
 | |
|     else {
 | |
|       this.pointers[pointer] = this.tails.array[index];
 | |
|       this.lengths.array[index]++;
 | |
|     }
 | |
| 
 | |
|     this.tails.array[index] = pointer;
 | |
|     this.items[pointer] = item;
 | |
|   }
 | |
|   else {
 | |
| 
 | |
|     // This linked list does not exist yet. Let's create it
 | |
|     if (index >= this.dimension) {
 | |
| 
 | |
|       // We may be required to grow the vectors
 | |
|       this.dimension = index + 1;
 | |
|       this.tails.grow(this.dimension);
 | |
|       this.lengths.grow(this.dimension);
 | |
| 
 | |
|       this.tails.resize(this.dimension);
 | |
|       this.lengths.resize(this.dimension);
 | |
| 
 | |
|       this.pointers.push(0);
 | |
|       this.lengths.array[index] = 1;
 | |
|     }
 | |
| 
 | |
|     // Appending to the list
 | |
|     else {
 | |
|       this.pointers.push(this.tails.array[index]);
 | |
|       this.lengths.array[index]++;
 | |
|     }
 | |
| 
 | |
|     this.tails.array[index] = pointer;
 | |
|     this.items.push(item);
 | |
|   }
 | |
| 
 | |
|   this.size++;
 | |
| 
 | |
|   return this;
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Method used to push a new container holding the given value.
 | |
|  * Note: it might be useful to make this function able to take an iterable
 | |
|  * or variadic someday. For the time being it's just a convenience for
 | |
|  * implementing compact multi maps and such.
 | |
|  *
 | |
|  * @param  {any} item  - Item to add.
 | |
|  * @return {MultiArray}
 | |
|  */
 | |
| MultiArray.prototype.push = function(item) {
 | |
|   var pointer = this.size,
 | |
|       index = this.dimension;
 | |
| 
 | |
|   if (this.hasFixedCapacity) {
 | |
| 
 | |
|     if (index >= this.capacity || this.size === this.capacity)
 | |
|       throw new Error('mnemonist/multi-array: attempting to allocate further than capacity.');
 | |
| 
 | |
|     this.items[pointer] = item;
 | |
|   }
 | |
|   else {
 | |
|     this.items.push(item);
 | |
|     this.pointers.push(0);
 | |
|   }
 | |
| 
 | |
|   this.lengths.push(1);
 | |
|   this.tails.push(pointer);
 | |
| 
 | |
|   this.dimension++;
 | |
|   this.size++;
 | |
| 
 | |
|   return this;
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Method used to get the desired container.
 | |
|  *
 | |
|  * @param  {number} index - Index of the container.
 | |
|  * @return {array}
 | |
|  */
 | |
| MultiArray.prototype.get = function(index) {
 | |
|   if (index >= this.dimension)
 | |
|     return;
 | |
| 
 | |
|   var pointers = this.hasFixedCapacity ? this.pointers : this.pointers.array;
 | |
| 
 | |
|   var pointer = this.tails.array[index],
 | |
|       length = this.lengths.array[index],
 | |
|       i = length;
 | |
| 
 | |
|   var array = new this.Container(length);
 | |
| 
 | |
|   while (i !== 0) {
 | |
|     array[--i] = this.items[pointer];
 | |
|     pointer = pointers[pointer];
 | |
|   }
 | |
| 
 | |
|   return array;
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Method used to check if a container exists at the given index.
 | |
|  *
 | |
|  * @param  {number} index - Index of the container.
 | |
|  * @return {boolean}
 | |
|  */
 | |
| MultiArray.prototype.has = function(index) {
 | |
|   return index < this.dimension;
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Method used to get the size of the container stored at given index.
 | |
|  *
 | |
|  * @param  {number} index - Index of the container.
 | |
|  * @return {number}
 | |
|  */
 | |
| MultiArray.prototype.multiplicity = function(index) {
 | |
|   if (index >= this.dimension)
 | |
|     return 0;
 | |
| 
 | |
|   return this.lengths.array[index];
 | |
| };
 | |
| MultiArray.prototype.count = MultiArray.prototype.multiplicity;
 | |
| 
 | |
| /**
 | |
|  * Method used to iterate over the structure's containers.
 | |
|  *
 | |
|  * @return {Iterator}
 | |
|  */
 | |
| MultiArray.prototype.containers = function() {
 | |
|   var self = this,
 | |
|       l = this.dimension,
 | |
|       i = 0;
 | |
| 
 | |
|   return new Iterator(function() {
 | |
|     if (i >= l)
 | |
|       return {done: true};
 | |
| 
 | |
|     return {value: self.get(i++)};
 | |
|   });
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Method used to iterate over the structure's associations.
 | |
|  *
 | |
|  * @return {Iterator}
 | |
|  */
 | |
| MultiArray.prototype.associations = function() {
 | |
|   var self = this,
 | |
|       l = this.dimension,
 | |
|       i = 0;
 | |
| 
 | |
|   return new Iterator(function() {
 | |
|     if (i >= l)
 | |
|       return {done: true};
 | |
| 
 | |
|     var data = {value: [i, self.get(i)]};
 | |
| 
 | |
|     i++;
 | |
| 
 | |
|     return data;
 | |
|   });
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Method used to iterate over the structure's values in the global insertion
 | |
|  * order.
 | |
|  *
 | |
|  * @param  {number}   [index] - Optionally, iterate over the values of a single
 | |
|  *                              container at index.
 | |
|  * @return {Iterator}
 | |
|  */
 | |
| MultiArray.prototype.values = function(index) {
 | |
|   var items = this.items,
 | |
|       length,
 | |
|       i = 0;
 | |
| 
 | |
|   if (typeof index === 'number') {
 | |
|     if (index >= this.dimension)
 | |
|       return Iterator.empty();
 | |
| 
 | |
|     length = this.lengths.array[index];
 | |
|     items = this.items;
 | |
| 
 | |
|     var pointers = this.hasFixedCapacity ? this.pointers : this.pointers.array;
 | |
| 
 | |
|     if (length === 0)
 | |
|       return Iterator.empty();
 | |
| 
 | |
|     var pointer = this.tails.array[index],
 | |
|         v;
 | |
| 
 | |
|     return new Iterator(function() {
 | |
|       if (i === length)
 | |
|         return {done: true};
 | |
| 
 | |
|       i++;
 | |
|       v = items[pointer];
 | |
|       pointer = pointers[pointer];
 | |
| 
 | |
|       return {done: false, value: v};
 | |
|     });
 | |
|   }
 | |
| 
 | |
|   length = this.size;
 | |
| 
 | |
|   return new Iterator(function() {
 | |
|     if (i >= length)
 | |
|       return {done: true};
 | |
| 
 | |
|     return {done: false, value: items[i++]};
 | |
|   });
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Method used to iterate over the structure's entries.
 | |
|  *
 | |
|  * @return {Iterator}
 | |
|  */
 | |
| MultiArray.prototype.entries = function() {
 | |
|   if (this.size === 0)
 | |
|     return Iterator.empty();
 | |
| 
 | |
|   var inContainer = false,
 | |
|       pointer,
 | |
|       length,
 | |
|       i = 0,
 | |
|       j = 0,
 | |
|       l = this.dimension,
 | |
|       v;
 | |
| 
 | |
|   var pointers = this.hasFixedCapacity ? this.pointers : this.pointers.array,
 | |
|       items = this.items,
 | |
|       tails = this.tails.array,
 | |
|       lengths = this.lengths.array;
 | |
| 
 | |
|   var iterator = new Iterator(function next() {
 | |
|     if (!inContainer) {
 | |
| 
 | |
|       if (i >= l)
 | |
|         return {done: true};
 | |
| 
 | |
|       length = lengths[i];
 | |
|       pointer = tails[i];
 | |
|       i++;
 | |
| 
 | |
|       if (length === 0)
 | |
|         return next();
 | |
| 
 | |
|       j = 0;
 | |
|       inContainer = true;
 | |
|     }
 | |
| 
 | |
|     if (j === length) {
 | |
|       inContainer = false;
 | |
|       return next();
 | |
|     }
 | |
| 
 | |
|     v = items[pointer];
 | |
| 
 | |
|     // TODO: guard for out-of-bounds
 | |
|     pointer = pointers[pointer];
 | |
| 
 | |
|     j++;
 | |
| 
 | |
|     return {
 | |
|       done: false,
 | |
|       value: [i - 1, v]
 | |
|     };
 | |
|   });
 | |
| 
 | |
|   return iterator;
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Method used to iterate over the structure's keys.
 | |
|  *
 | |
|  * @return {Iterator}
 | |
|  */
 | |
| MultiArray.prototype.keys = function() {
 | |
|   var i = 0,
 | |
|       l = this.dimension;
 | |
| 
 | |
|   return new Iterator(function() {
 | |
|     if (i >= l)
 | |
|       return {done: true};
 | |
| 
 | |
|     return {done: false, value: i++};
 | |
|   });
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Convenience known methods.
 | |
|  */
 | |
| MultiArray.prototype.inspect = function() {
 | |
|   var proxy = new Array(this.dimension),
 | |
|       i,
 | |
|       l;
 | |
| 
 | |
|   for (i = 0, l = this.dimension; i < l; i++)
 | |
|     proxy[i] = Array.from(this.get(i));
 | |
| 
 | |
|   if (this.hasFixedCapacity) {
 | |
|     proxy.type = this.Container.name;
 | |
|     proxy.capacity = this.capacity;
 | |
|   }
 | |
| 
 | |
|   proxy.size = this.size;
 | |
|   proxy.dimension = this.dimension;
 | |
| 
 | |
|   // Trick so that node displays the name of the constructor
 | |
|   Object.defineProperty(proxy, 'constructor', {
 | |
|     value: MultiArray,
 | |
|     enumerable: false
 | |
|   });
 | |
| 
 | |
|   return proxy;
 | |
| };
 | |
| 
 | |
| if (typeof Symbol !== 'undefined')
 | |
|   MultiArray.prototype[Symbol.for('nodejs.util.inspect.custom')] = MultiArray.prototype.inspect;
 | |
| 
 | |
| // TODO: .from
 | |
| 
 | |
| /**
 | |
|  * Exporting.
 | |
|  */
 | |
| module.exports = MultiArray;
 |