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