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.9 KiB
			
		
		
			
		
	
	
					210 lines
				
				4.9 KiB
			| 
											3 years ago
										 | /** | ||
|  |  * Mnemonist Fixed Reverse Heap | ||
|  |  * ============================= | ||
|  |  * | ||
|  |  * Static heap implementation with fixed capacity. It's a "reverse" heap | ||
|  |  * because it stores the elements in reverse so we can replace the worst | ||
|  |  * item in logarithmic time. As such, one cannot pop this heap but can only | ||
|  |  * consume it at the end. This structure is very efficient when trying to | ||
|  |  * find the n smallest/largest items from a larger query (k nearest neigbors | ||
|  |  * for instance). | ||
|  |  */ | ||
|  | var comparators = require('./utils/comparators.js'), | ||
|  |     Heap = require('./heap.js'); | ||
|  | 
 | ||
|  | var DEFAULT_COMPARATOR = comparators.DEFAULT_COMPARATOR, | ||
|  |     reverseComparator = comparators.reverseComparator; | ||
|  | 
 | ||
|  | /** | ||
|  |  * Helper functions. | ||
|  |  */ | ||
|  | 
 | ||
|  | /** | ||
|  |  * Function used to sift up. | ||
|  |  * | ||
|  |  * @param {function} compare - Comparison function. | ||
|  |  * @param {array}    heap    - Array storing the heap's data. | ||
|  |  * @param {number}   size    - Heap's true size. | ||
|  |  * @param {number}   i       - Index. | ||
|  |  */ | ||
|  | function siftUp(compare, heap, size, i) { | ||
|  |   var endIndex = size, | ||
|  |       startIndex = i, | ||
|  |       item = heap[i], | ||
|  |       childIndex = 2 * i + 1, | ||
|  |       rightIndex; | ||
|  | 
 | ||
|  |   while (childIndex < endIndex) { | ||
|  |     rightIndex = childIndex + 1; | ||
|  | 
 | ||
|  |     if ( | ||
|  |       rightIndex < endIndex && | ||
|  |       compare(heap[childIndex], heap[rightIndex]) >= 0 | ||
|  |     ) { | ||
|  |       childIndex = rightIndex; | ||
|  |     } | ||
|  | 
 | ||
|  |     heap[i] = heap[childIndex]; | ||
|  |     i = childIndex; | ||
|  |     childIndex = 2 * i + 1; | ||
|  |   } | ||
|  | 
 | ||
|  |   heap[i] = item; | ||
|  |   Heap.siftDown(compare, heap, startIndex, i); | ||
|  | } | ||
|  | 
 | ||
|  | /** | ||
|  |  * Fully consumes the given heap. | ||
|  |  * | ||
|  |  * @param  {function} ArrayClass - Array class to use. | ||
|  |  * @param  {function} compare    - Comparison function. | ||
|  |  * @param  {array}    heap       - Array storing the heap's data. | ||
|  |  * @param  {number}   size       - True size of the heap. | ||
|  |  * @return {array} | ||
|  |  */ | ||
|  | function consume(ArrayClass, compare, heap, size) { | ||
|  |   var l = size, | ||
|  |       i = l; | ||
|  | 
 | ||
|  |   var array = new ArrayClass(size), | ||
|  |       lastItem, | ||
|  |       item; | ||
|  | 
 | ||
|  |   while (i > 0) { | ||
|  |     lastItem = heap[--i]; | ||
|  | 
 | ||
|  |     if (i !== 0) { | ||
|  |       item = heap[0]; | ||
|  |       heap[0] = lastItem; | ||
|  |       siftUp(compare, heap, --size, 0); | ||
|  |       lastItem = item; | ||
|  |     } | ||
|  | 
 | ||
|  |     array[i] = lastItem; | ||
|  |   } | ||
|  | 
 | ||
|  |   return array; | ||
|  | } | ||
|  | 
 | ||
|  | /** | ||
|  |  * Binary Minimum FixedReverseHeap. | ||
|  |  * | ||
|  |  * @constructor | ||
|  |  * @param {function} ArrayClass - The class of array to use. | ||
|  |  * @param {function} comparator - Comparator function. | ||
|  |  * @param {number}   capacity   - Maximum number of items to keep. | ||
|  |  */ | ||
|  | function FixedReverseHeap(ArrayClass, comparator, capacity) { | ||
|  | 
 | ||
|  |   // Comparator can be omitted
 | ||
|  |   if (arguments.length === 2) { | ||
|  |     capacity = comparator; | ||
|  |     comparator = null; | ||
|  |   } | ||
|  | 
 | ||
|  |   this.ArrayClass = ArrayClass; | ||
|  |   this.capacity = capacity; | ||
|  | 
 | ||
|  |   this.items = new ArrayClass(capacity); | ||
|  |   this.clear(); | ||
|  |   this.comparator = comparator || DEFAULT_COMPARATOR; | ||
|  | 
 | ||
|  |   if (typeof capacity !== 'number' && capacity <= 0) | ||
|  |     throw new Error('mnemonist/FixedReverseHeap.constructor: capacity should be a number > 0.'); | ||
|  | 
 | ||
|  |   if (typeof this.comparator !== 'function') | ||
|  |     throw new Error('mnemonist/FixedReverseHeap.constructor: given comparator should be a function.'); | ||
|  | 
 | ||
|  |   this.comparator = reverseComparator(this.comparator); | ||
|  | } | ||
|  | 
 | ||
|  | /** | ||
|  |  * Method used to clear the heap. | ||
|  |  * | ||
|  |  * @return {undefined} | ||
|  |  */ | ||
|  | FixedReverseHeap.prototype.clear = function() { | ||
|  | 
 | ||
|  |   // Properties
 | ||
|  |   this.size = 0; | ||
|  | }; | ||
|  | 
 | ||
|  | /** | ||
|  |  * Method used to push an item into the heap. | ||
|  |  * | ||
|  |  * @param  {any}    item - Item to push. | ||
|  |  * @return {number} | ||
|  |  */ | ||
|  | FixedReverseHeap.prototype.push = function(item) { | ||
|  | 
 | ||
|  |   // Still some place
 | ||
|  |   if (this.size < this.capacity) { | ||
|  |     this.items[this.size] = item; | ||
|  |     Heap.siftDown(this.comparator, this.items, 0, this.size); | ||
|  |     this.size++; | ||
|  |   } | ||
|  | 
 | ||
|  |   // Heap is full, we need to replace worst item
 | ||
|  |   else { | ||
|  | 
 | ||
|  |     if (this.comparator(item, this.items[0]) > 0) | ||
|  |       Heap.replace(this.comparator, this.items, item); | ||
|  |   } | ||
|  | 
 | ||
|  |   return this.size; | ||
|  | }; | ||
|  | 
 | ||
|  | /** | ||
|  |  * Method used to peek the worst item in the heap. | ||
|  |  * | ||
|  |  * @return {any} | ||
|  |  */ | ||
|  | FixedReverseHeap.prototype.peek = function() { | ||
|  |   return this.items[0]; | ||
|  | }; | ||
|  | 
 | ||
|  | /** | ||
|  |  * Method used to consume the heap fully and return its items as a sorted array. | ||
|  |  * | ||
|  |  * @return {array} | ||
|  |  */ | ||
|  | FixedReverseHeap.prototype.consume = function() { | ||
|  |   var items = consume(this.ArrayClass, this.comparator, this.items, this.size); | ||
|  |   this.size = 0; | ||
|  | 
 | ||
|  |   return items; | ||
|  | }; | ||
|  | 
 | ||
|  | /** | ||
|  |  * Method used to convert the heap to an array. Note that it basically clone | ||
|  |  * the heap and consumes it completely. This is hardly performant. | ||
|  |  * | ||
|  |  * @return {array} | ||
|  |  */ | ||
|  | FixedReverseHeap.prototype.toArray = function() { | ||
|  |   return consume(this.ArrayClass, this.comparator, this.items.slice(0, this.size), this.size); | ||
|  | }; | ||
|  | 
 | ||
|  | /** | ||
|  |  * Convenience known methods. | ||
|  |  */ | ||
|  | FixedReverseHeap.prototype.inspect = function() { | ||
|  |   var proxy = this.toArray(); | ||
|  | 
 | ||
|  |   // Trick so that node displays the name of the constructor
 | ||
|  |   Object.defineProperty(proxy, 'constructor', { | ||
|  |     value: FixedReverseHeap, | ||
|  |     enumerable: false | ||
|  |   }); | ||
|  | 
 | ||
|  |   return proxy; | ||
|  | }; | ||
|  | 
 | ||
|  | if (typeof Symbol !== 'undefined') | ||
|  |   FixedReverseHeap.prototype[Symbol.for('nodejs.util.inspect.custom')] = FixedReverseHeap.prototype.inspect; | ||
|  | 
 | ||
|  | /** | ||
|  |  * Exporting. | ||
|  |  */ | ||
|  | module.exports = FixedReverseHeap; |