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.
		
		
		
		
		
			
		
			
				
					577 lines
				
				12 KiB
			
		
		
			
		
	
	
					577 lines
				
				12 KiB
			| 
											3 years ago
										 | /** | ||
|  |  * Mnemonist Binary Heap | ||
|  |  * ====================== | ||
|  |  * | ||
|  |  * Binary heap implementation. | ||
|  |  */ | ||
|  | var forEach = require('obliterator/foreach'), | ||
|  |     comparators = require('./utils/comparators.js'), | ||
|  |     iterables = require('./utils/iterables.js'); | ||
|  | 
 | ||
|  | var DEFAULT_COMPARATOR = comparators.DEFAULT_COMPARATOR, | ||
|  |     reverseComparator = comparators.reverseComparator; | ||
|  | 
 | ||
|  | /** | ||
|  |  * Heap helper functions. | ||
|  |  */ | ||
|  | 
 | ||
|  | /** | ||
|  |  * Function used to sift down. | ||
|  |  * | ||
|  |  * @param {function} compare    - Comparison function. | ||
|  |  * @param {array}    heap       - Array storing the heap's data. | ||
|  |  * @param {number}   startIndex - Starting index. | ||
|  |  * @param {number}   i          - Index. | ||
|  |  */ | ||
|  | function siftDown(compare, heap, startIndex, i) { | ||
|  |   var item = heap[i], | ||
|  |       parentIndex, | ||
|  |       parent; | ||
|  | 
 | ||
|  |   while (i > startIndex) { | ||
|  |     parentIndex = (i - 1) >> 1; | ||
|  |     parent = heap[parentIndex]; | ||
|  | 
 | ||
|  |     if (compare(item, parent) < 0) { | ||
|  |       heap[i] = parent; | ||
|  |       i = parentIndex; | ||
|  |       continue; | ||
|  |     } | ||
|  | 
 | ||
|  |     break; | ||
|  |   } | ||
|  | 
 | ||
|  |   heap[i] = item; | ||
|  | } | ||
|  | 
 | ||
|  | /** | ||
|  |  * Function used to sift up. | ||
|  |  * | ||
|  |  * @param {function} compare - Comparison function. | ||
|  |  * @param {array}    heap    - Array storing the heap's data. | ||
|  |  * @param {number}   i       - Index. | ||
|  |  */ | ||
|  | function siftUp(compare, heap, i) { | ||
|  |   var endIndex = heap.length, | ||
|  |       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; | ||
|  |   siftDown(compare, heap, startIndex, i); | ||
|  | } | ||
|  | 
 | ||
|  | /** | ||
|  |  * Function used to push an item into a heap represented by a raw array. | ||
|  |  * | ||
|  |  * @param {function} compare - Comparison function. | ||
|  |  * @param {array}    heap    - Array storing the heap's data. | ||
|  |  * @param {any}      item    - Item to push. | ||
|  |  */ | ||
|  | function push(compare, heap, item) { | ||
|  |   heap.push(item); | ||
|  |   siftDown(compare, heap, 0, heap.length - 1); | ||
|  | } | ||
|  | 
 | ||
|  | /** | ||
|  |  * Function used to pop an item from a heap represented by a raw array. | ||
|  |  * | ||
|  |  * @param  {function} compare - Comparison function. | ||
|  |  * @param  {array}    heap    - Array storing the heap's data. | ||
|  |  * @return {any} | ||
|  |  */ | ||
|  | function pop(compare, heap) { | ||
|  |   var lastItem = heap.pop(); | ||
|  | 
 | ||
|  |   if (heap.length !== 0) { | ||
|  |     var item = heap[0]; | ||
|  |     heap[0] = lastItem; | ||
|  |     siftUp(compare, heap, 0); | ||
|  | 
 | ||
|  |     return item; | ||
|  |   } | ||
|  | 
 | ||
|  |   return lastItem; | ||
|  | } | ||
|  | 
 | ||
|  | /** | ||
|  |  * Function used to pop the heap then push a new value into it, thus "replacing" | ||
|  |  * it. | ||
|  |  * | ||
|  |  * @param  {function} compare - Comparison function. | ||
|  |  * @param  {array}    heap    - Array storing the heap's data. | ||
|  |  * @param  {any}      item    - The item to push. | ||
|  |  * @return {any} | ||
|  |  */ | ||
|  | function replace(compare, heap, item) { | ||
|  |   if (heap.length === 0) | ||
|  |     throw new Error('mnemonist/heap.replace: cannot pop an empty heap.'); | ||
|  | 
 | ||
|  |   var popped = heap[0]; | ||
|  |   heap[0] = item; | ||
|  |   siftUp(compare, heap, 0); | ||
|  | 
 | ||
|  |   return popped; | ||
|  | } | ||
|  | 
 | ||
|  | /** | ||
|  |  * Function used to push an item in the heap then pop the heap and return the | ||
|  |  * popped value. | ||
|  |  * | ||
|  |  * @param  {function} compare - Comparison function. | ||
|  |  * @param  {array}    heap    - Array storing the heap's data. | ||
|  |  * @param  {any}      item    - The item to push. | ||
|  |  * @return {any} | ||
|  |  */ | ||
|  | function pushpop(compare, heap, item) { | ||
|  |   var tmp; | ||
|  | 
 | ||
|  |   if (heap.length !== 0 && compare(heap[0], item) < 0) { | ||
|  |     tmp = heap[0]; | ||
|  |     heap[0] = item; | ||
|  |     item = tmp; | ||
|  |     siftUp(compare, heap, 0); | ||
|  |   } | ||
|  | 
 | ||
|  |   return item; | ||
|  | } | ||
|  | 
 | ||
|  | /** | ||
|  |  * Converts and array into an abstract heap in linear time. | ||
|  |  * | ||
|  |  * @param {function} compare - Comparison function. | ||
|  |  * @param {array}    array   - Target array. | ||
|  |  */ | ||
|  | function heapify(compare, array) { | ||
|  |   var n = array.length, | ||
|  |       l = n >> 1, | ||
|  |       i = l; | ||
|  | 
 | ||
|  |   while (--i >= 0) | ||
|  |     siftUp(compare, array, i); | ||
|  | } | ||
|  | 
 | ||
|  | /** | ||
|  |  * Fully consumes the given heap. | ||
|  |  * | ||
|  |  * @param  {function} compare - Comparison function. | ||
|  |  * @param  {array}    heap    - Array storing the heap's data. | ||
|  |  * @return {array} | ||
|  |  */ | ||
|  | function consume(compare, heap) { | ||
|  |   var l = heap.length, | ||
|  |       i = 0; | ||
|  | 
 | ||
|  |   var array = new Array(l); | ||
|  | 
 | ||
|  |   while (i < l) | ||
|  |     array[i++] = pop(compare, heap); | ||
|  | 
 | ||
|  |   return array; | ||
|  | } | ||
|  | 
 | ||
|  | /** | ||
|  |  * Function used to retrieve the n smallest items from the given iterable. | ||
|  |  * | ||
|  |  * @param {function} compare  - Comparison function. | ||
|  |  * @param {number}   n        - Number of top items to retrieve. | ||
|  |  * @param {any}      iterable - Arbitrary iterable. | ||
|  |  * @param {array} | ||
|  |  */ | ||
|  | function nsmallest(compare, n, iterable) { | ||
|  |   if (arguments.length === 2) { | ||
|  |     iterable = n; | ||
|  |     n = compare; | ||
|  |     compare = DEFAULT_COMPARATOR; | ||
|  |   } | ||
|  | 
 | ||
|  |   var reverseCompare = reverseComparator(compare); | ||
|  | 
 | ||
|  |   var i, l, v; | ||
|  | 
 | ||
|  |   var min = Infinity; | ||
|  | 
 | ||
|  |   var result; | ||
|  | 
 | ||
|  |   // If n is equal to 1, it's just a matter of finding the minimum
 | ||
|  |   if (n === 1) { | ||
|  |     if (iterables.isArrayLike(iterable)) { | ||
|  |       for (i = 0, l = iterable.length; i < l; i++) { | ||
|  |         v = iterable[i]; | ||
|  | 
 | ||
|  |         if (min === Infinity || compare(v, min) < 0) | ||
|  |           min = v; | ||
|  |       } | ||
|  | 
 | ||
|  |       result = new iterable.constructor(1); | ||
|  |       result[0] = min; | ||
|  | 
 | ||
|  |       return result; | ||
|  |     } | ||
|  | 
 | ||
|  |     forEach(iterable, function(value) { | ||
|  |       if (min === Infinity || compare(value, min) < 0) | ||
|  |         min = value; | ||
|  |     }); | ||
|  | 
 | ||
|  |     return [min]; | ||
|  |   } | ||
|  | 
 | ||
|  |   if (iterables.isArrayLike(iterable)) { | ||
|  | 
 | ||
|  |     // If n > iterable length, we just clone and sort
 | ||
|  |     if (n >= iterable.length) | ||
|  |       return iterable.slice().sort(compare); | ||
|  | 
 | ||
|  |     result = iterable.slice(0, n); | ||
|  |     heapify(reverseCompare, result); | ||
|  | 
 | ||
|  |     for (i = n, l = iterable.length; i < l; i++) | ||
|  |       if (reverseCompare(iterable[i], result[0]) > 0) | ||
|  |         replace(reverseCompare, result, iterable[i]); | ||
|  | 
 | ||
|  |     // NOTE: if n is over some number, it becomes faster to consume the heap
 | ||
|  |     return result.sort(compare); | ||
|  |   } | ||
|  | 
 | ||
|  |   // Correct for size
 | ||
|  |   var size = iterables.guessLength(iterable); | ||
|  | 
 | ||
|  |   if (size !== null && size < n) | ||
|  |     n = size; | ||
|  | 
 | ||
|  |   result = new Array(n); | ||
|  |   i = 0; | ||
|  | 
 | ||
|  |   forEach(iterable, function(value) { | ||
|  |     if (i < n) { | ||
|  |       result[i] = value; | ||
|  |     } | ||
|  |     else { | ||
|  |       if (i === n) | ||
|  |         heapify(reverseCompare, result); | ||
|  | 
 | ||
|  |       if (reverseCompare(value, result[0]) > 0) | ||
|  |         replace(reverseCompare, result, value); | ||
|  |     } | ||
|  | 
 | ||
|  |     i++; | ||
|  |   }); | ||
|  | 
 | ||
|  |   if (result.length > i) | ||
|  |     result.length = i; | ||
|  | 
 | ||
|  |   // NOTE: if n is over some number, it becomes faster to consume the heap
 | ||
|  |   return result.sort(compare); | ||
|  | } | ||
|  | 
 | ||
|  | /** | ||
|  |  * Function used to retrieve the n largest items from the given iterable. | ||
|  |  * | ||
|  |  * @param {function} compare  - Comparison function. | ||
|  |  * @param {number}   n        - Number of top items to retrieve. | ||
|  |  * @param {any}      iterable - Arbitrary iterable. | ||
|  |  * @param {array} | ||
|  |  */ | ||
|  | function nlargest(compare, n, iterable) { | ||
|  |   if (arguments.length === 2) { | ||
|  |     iterable = n; | ||
|  |     n = compare; | ||
|  |     compare = DEFAULT_COMPARATOR; | ||
|  |   } | ||
|  | 
 | ||
|  |   var reverseCompare = reverseComparator(compare); | ||
|  | 
 | ||
|  |   var i, l, v; | ||
|  | 
 | ||
|  |   var max = -Infinity; | ||
|  | 
 | ||
|  |   var result; | ||
|  | 
 | ||
|  |   // If n is equal to 1, it's just a matter of finding the maximum
 | ||
|  |   if (n === 1) { | ||
|  |     if (iterables.isArrayLike(iterable)) { | ||
|  |       for (i = 0, l = iterable.length; i < l; i++) { | ||
|  |         v = iterable[i]; | ||
|  | 
 | ||
|  |         if (max === -Infinity || compare(v, max) > 0) | ||
|  |           max = v; | ||
|  |       } | ||
|  | 
 | ||
|  |       result = new iterable.constructor(1); | ||
|  |       result[0] = max; | ||
|  | 
 | ||
|  |       return result; | ||
|  |     } | ||
|  | 
 | ||
|  |     forEach(iterable, function(value) { | ||
|  |       if (max === -Infinity || compare(value, max) > 0) | ||
|  |         max = value; | ||
|  |     }); | ||
|  | 
 | ||
|  |     return [max]; | ||
|  |   } | ||
|  | 
 | ||
|  |   if (iterables.isArrayLike(iterable)) { | ||
|  | 
 | ||
|  |     // If n > iterable length, we just clone and sort
 | ||
|  |     if (n >= iterable.length) | ||
|  |       return iterable.slice().sort(reverseCompare); | ||
|  | 
 | ||
|  |     result = iterable.slice(0, n); | ||
|  |     heapify(compare, result); | ||
|  | 
 | ||
|  |     for (i = n, l = iterable.length; i < l; i++) | ||
|  |       if (compare(iterable[i], result[0]) > 0) | ||
|  |         replace(compare, result, iterable[i]); | ||
|  | 
 | ||
|  |     // NOTE: if n is over some number, it becomes faster to consume the heap
 | ||
|  |     return result.sort(reverseCompare); | ||
|  |   } | ||
|  | 
 | ||
|  |   // Correct for size
 | ||
|  |   var size = iterables.guessLength(iterable); | ||
|  | 
 | ||
|  |   if (size !== null && size < n) | ||
|  |     n = size; | ||
|  | 
 | ||
|  |   result = new Array(n); | ||
|  |   i = 0; | ||
|  | 
 | ||
|  |   forEach(iterable, function(value) { | ||
|  |     if (i < n) { | ||
|  |       result[i] = value; | ||
|  |     } | ||
|  |     else { | ||
|  |       if (i === n) | ||
|  |         heapify(compare, result); | ||
|  | 
 | ||
|  |       if (compare(value, result[0]) > 0) | ||
|  |         replace(compare, result, value); | ||
|  |     } | ||
|  | 
 | ||
|  |     i++; | ||
|  |   }); | ||
|  | 
 | ||
|  |   if (result.length > i) | ||
|  |     result.length = i; | ||
|  | 
 | ||
|  |   // NOTE: if n is over some number, it becomes faster to consume the heap
 | ||
|  |   return result.sort(reverseCompare); | ||
|  | } | ||
|  | 
 | ||
|  | /** | ||
|  |  * Binary Minimum Heap. | ||
|  |  * | ||
|  |  * @constructor | ||
|  |  * @param {function} comparator - Comparator function to use. | ||
|  |  */ | ||
|  | function Heap(comparator) { | ||
|  |   this.clear(); | ||
|  |   this.comparator = comparator || DEFAULT_COMPARATOR; | ||
|  | 
 | ||
|  |   if (typeof this.comparator !== 'function') | ||
|  |     throw new Error('mnemonist/Heap.constructor: given comparator should be a function.'); | ||
|  | } | ||
|  | 
 | ||
|  | /** | ||
|  |  * Method used to clear the heap. | ||
|  |  * | ||
|  |  * @return {undefined} | ||
|  |  */ | ||
|  | Heap.prototype.clear = function() { | ||
|  | 
 | ||
|  |   // Properties
 | ||
|  |   this.items = []; | ||
|  |   this.size = 0; | ||
|  | }; | ||
|  | 
 | ||
|  | /** | ||
|  |  * Method used to push an item into the heap. | ||
|  |  * | ||
|  |  * @param  {any}    item - Item to push. | ||
|  |  * @return {number} | ||
|  |  */ | ||
|  | Heap.prototype.push = function(item) { | ||
|  |   push(this.comparator, this.items, item); | ||
|  |   return ++this.size; | ||
|  | }; | ||
|  | 
 | ||
|  | /** | ||
|  |  * Method used to retrieve the "first" item of the heap. | ||
|  |  * | ||
|  |  * @return {any} | ||
|  |  */ | ||
|  | Heap.prototype.peek = function() { | ||
|  |   return this.items[0]; | ||
|  | }; | ||
|  | 
 | ||
|  | /** | ||
|  |  * Method used to retrieve & remove the "first" item of the heap. | ||
|  |  * | ||
|  |  * @return {any} | ||
|  |  */ | ||
|  | Heap.prototype.pop = function() { | ||
|  |   if (this.size !== 0) | ||
|  |     this.size--; | ||
|  | 
 | ||
|  |   return pop(this.comparator, this.items); | ||
|  | }; | ||
|  | 
 | ||
|  | /** | ||
|  |  * Method used to pop the heap, then push an item and return the popped | ||
|  |  * item. | ||
|  |  * | ||
|  |  * @param  {any} item - Item to push into the heap. | ||
|  |  * @return {any} | ||
|  |  */ | ||
|  | Heap.prototype.replace = function(item) { | ||
|  |   return replace(this.comparator, this.items, item); | ||
|  | }; | ||
|  | 
 | ||
|  | /** | ||
|  |  * Method used to push the heap, the pop it and return the pooped item. | ||
|  |  * | ||
|  |  * @param  {any} item - Item to push into the heap. | ||
|  |  * @return {any} | ||
|  |  */ | ||
|  | Heap.prototype.pushpop = function(item) { | ||
|  |   return pushpop(this.comparator, this.items, item); | ||
|  | }; | ||
|  | 
 | ||
|  | /** | ||
|  |  * Method used to consume the heap fully and return its items as a sorted array. | ||
|  |  * | ||
|  |  * @return {array} | ||
|  |  */ | ||
|  | Heap.prototype.consume = function() { | ||
|  |   this.size = 0; | ||
|  |   return consume(this.comparator, this.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} | ||
|  |  */ | ||
|  | Heap.prototype.toArray = function() { | ||
|  |   return consume(this.comparator, this.items.slice()); | ||
|  | }; | ||
|  | 
 | ||
|  | /** | ||
|  |  * Convenience known methods. | ||
|  |  */ | ||
|  | Heap.prototype.inspect = function() { | ||
|  |   var proxy = this.toArray(); | ||
|  | 
 | ||
|  |   // Trick so that node displays the name of the constructor
 | ||
|  |   Object.defineProperty(proxy, 'constructor', { | ||
|  |     value: Heap, | ||
|  |     enumerable: false | ||
|  |   }); | ||
|  | 
 | ||
|  |   return proxy; | ||
|  | }; | ||
|  | 
 | ||
|  | if (typeof Symbol !== 'undefined') | ||
|  |   Heap.prototype[Symbol.for('nodejs.util.inspect.custom')] = Heap.prototype.inspect; | ||
|  | 
 | ||
|  | /** | ||
|  |  * Binary Maximum Heap. | ||
|  |  * | ||
|  |  * @constructor | ||
|  |  * @param {function} comparator - Comparator function to use. | ||
|  |  */ | ||
|  | function MaxHeap(comparator) { | ||
|  |   this.clear(); | ||
|  |   this.comparator = comparator || DEFAULT_COMPARATOR; | ||
|  | 
 | ||
|  |   if (typeof this.comparator !== 'function') | ||
|  |     throw new Error('mnemonist/MaxHeap.constructor: given comparator should be a function.'); | ||
|  | 
 | ||
|  |   this.comparator = reverseComparator(this.comparator); | ||
|  | } | ||
|  | 
 | ||
|  | MaxHeap.prototype = Heap.prototype; | ||
|  | 
 | ||
|  | /** | ||
|  |  * Static @.from function taking an arbitrary iterable & converting it into | ||
|  |  * a heap. | ||
|  |  * | ||
|  |  * @param  {Iterable} iterable   - Target iterable. | ||
|  |  * @param  {function} comparator - Custom comparator function. | ||
|  |  * @return {Heap} | ||
|  |  */ | ||
|  | Heap.from = function(iterable, comparator) { | ||
|  |   var heap = new Heap(comparator); | ||
|  | 
 | ||
|  |   var items; | ||
|  | 
 | ||
|  |   // If iterable is an array, we can be clever about it
 | ||
|  |   if (iterables.isArrayLike(iterable)) | ||
|  |     items = iterable.slice(); | ||
|  |   else | ||
|  |     items = iterables.toArray(iterable); | ||
|  | 
 | ||
|  |   heapify(heap.comparator, items); | ||
|  |   heap.items = items; | ||
|  |   heap.size = items.length; | ||
|  | 
 | ||
|  |   return heap; | ||
|  | }; | ||
|  | 
 | ||
|  | MaxHeap.from = function(iterable, comparator) { | ||
|  |   var heap = new MaxHeap(comparator); | ||
|  | 
 | ||
|  |   var items; | ||
|  | 
 | ||
|  |   // If iterable is an array, we can be clever about it
 | ||
|  |   if (iterables.isArrayLike(iterable)) | ||
|  |     items = iterable.slice(); | ||
|  |   else | ||
|  |     items = iterables.toArray(iterable); | ||
|  | 
 | ||
|  |   heapify(heap.comparator, items); | ||
|  |   heap.items = items; | ||
|  |   heap.size = items.length; | ||
|  | 
 | ||
|  |   return heap; | ||
|  | }; | ||
|  | 
 | ||
|  | /** | ||
|  |  * Exporting. | ||
|  |  */ | ||
|  | Heap.siftUp = siftUp; | ||
|  | Heap.siftDown = siftDown; | ||
|  | Heap.push = push; | ||
|  | Heap.pop = pop; | ||
|  | Heap.replace = replace; | ||
|  | Heap.pushpop = pushpop; | ||
|  | Heap.heapify = heapify; | ||
|  | Heap.consume = consume; | ||
|  | 
 | ||
|  | Heap.nsmallest = nsmallest; | ||
|  | Heap.nlargest = nlargest; | ||
|  | 
 | ||
|  | Heap.MinHeap = Heap; | ||
|  | Heap.MaxHeap = MaxHeap; | ||
|  | 
 | ||
|  | module.exports = Heap; |