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