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