/**
* Heap
* @type {Class}
*/
var Heap = (function () {
/**
* Heap instance constructor.
* @param {Function} compare Optional comparison function, defaults to Heap.minComparator<number>
*/
function Heap(compare) {
if (compare === void 0) { compare = Heap.minComparator; }
var _this = this;
this.heapArray = [];
this._limit = null;
/**
* Alias of add
*/
this.offer = this.add;
/**
* Alias of peek
*/
this.element = this.peek;
/**
* Alias of pop
*/
this.poll = this.pop;
/**
* Returns the inverse to the comparison function.
* @return {Function}
*/
this._invertedCompare = function (a, b) {
return -1 * _this.compare(a, b);
};
this.compare = compare;
}
/*
Static methods
*/
/**
* Gets children indices for given index.
* @param {Number} idx Parent index
* @return {Array(Number)} Array of children indices
*/
Heap.getChildrenIndexOf = function (idx) {
return [idx * 2 + 1, idx * 2 + 2];
};
/**
* Gets parent index for given index.
* @param {Number} idx Children index
* @return {Number | undefined} Parent index, -1 if idx is 0
*/
Heap.getParentIndexOf = function (idx) {
if (idx <= 0) {
return -1;
}
var whichChildren = idx % 2 ? 1 : 2;
return Math.floor((idx - whichChildren) / 2);
};
/**
* Gets sibling index for given index.
* @param {Number} idx Children index
* @return {Number | undefined} Sibling index, -1 if idx is 0
*/
Heap.getSiblingIndexOf = function (idx) {
if (idx <= 0) {
return -1;
}
var whichChildren = idx % 2 ? 1 : -1;
return idx + whichChildren;
};
/**
* Min heap comparison function, default.
* @param {any} a First element
* @param {any} b Second element
* @return {Number} 0 if they're equal, positive if `a` goes up, negative if `b` goes up
*/
Heap.minComparator = function (a, b) {
return a - b;
};
/**
* Max heap comparison function.
* @param {any} a First element
* @param {any} b Second element
* @return {Number} 0 if they're equal, positive if `a` goes up, negative if `b` goes up
*/
Heap.maxComparator = function (a, b) {
return b - a;
};
/**
* Default equality function.
* @param {any} a First element
* @param {any} b Second element
* @return {Boolean} True if equal, false otherwise
*/
Heap.defaultIsEqual = function (a, b) {
return a === b;
};
/**
* Prints a heap.
* @param {Heap} heap Heap to be printed
* @returns {String}
*/
Heap.print = function (heap) {
function deep(i) {
var pi = Heap.getParentIndexOf(i);
return Math.floor(Math.log2(pi + 1));
}
function repeat(str, times) {
var out = "";
for (; times > 0; --times) {
out += str;
}
return out;
}
var node = 0;
var lines = [];
var maxLines = deep(heap.length - 1) + 2;
var maxLength = 0;
while (node < heap.length) {
var i = deep(node) + 1;
if (node === 0) {
i = 0;
}
// Text representation
var nodeText = heap.get(node).toString();
if (nodeText.length > maxLength) {
maxLength = nodeText.length;
}
// Add to line
lines[i] = lines[i] || [];
lines[i].push(nodeText);
node += 1;
}
return lines
.map(function (line, i) {
var times = Math.pow(2, maxLines - i) - 1;
return (repeat(" ", Math.floor(times / 2) * maxLength) +
line
.map(function (el) {
// centered
var half = (maxLength - el.length) / 2;
return (repeat(" ", Math.ceil(half)) +
el +
repeat(" ", Math.floor(half)));
})
.join(repeat(" ", times * maxLength)));
})
.join("\n");
};
/*
Python style
*/
/**
* Converts an array into an array-heap
* @param {Array} arr Array to be modified
* @param {Function} compare Optional compare function
* @return {Heap} For convenience, it returns a Heap instance
*/
Heap.heapify = function (arr, compare) {
var heap = new Heap(compare);
heap.heapArray = arr;
heap.init();
return heap;
};
/**
* Extract the peek of an array-heap
* @param {Array} heapArr Array to be modified, should be a heap
* @param {Function} compare Optional compare function
* @return {any} Returns the extracted peek
*/
Heap.heappop = function (heapArr, compare) {
var heap = new Heap(compare);
heap.heapArray = heapArr;
return heap.pop();
};
/**
* Pushes a item into an array-heap
* @param {Array} heapArr Array to be modified, should be a heap
* @param {any} item Item to push
* @param {Function} compare Optional compare function
*/
Heap.heappush = function (heapArr, item, compare) {
var heap = new Heap(compare);
heap.heapArray = heapArr;
heap.push(item);
};
/**
* Push followed by pop, faster
* @param {Array} heapArr Array to be modified, should be a heap
* @param {any} item Item to push
* @param {Function} compare Optional compare function
* @return {any} Returns the extracted peek
*/
Heap.heappushpop = function (heapArr, item, compare) {
var heap = new Heap(compare);
heap.heapArray = heapArr;
return heap.pushpop(item);
};
/**
* Replace peek with item
* @param {Array} heapArr Array to be modified, should be a heap
* @param {any} item Item as replacement
* @param {Function} compare Optional compare function
* @return {any} Returns the extracted peek
*/
Heap.heapreplace = function (heapArr, item, compare) {
var heap = new Heap(compare);
heap.heapArray = heapArr;
return heap.replace(item);
};
/**
* Return the `n` most valuable elements
* @param {Array} heapArr Array, should be a heap
* @param {number} n Max number of elements
* @param {Function} compare Optional compare function
* @return {any} Elements
*/
Heap.heaptop = function (heapArr, n, compare) {
if (n === void 0) { n = 1; }
var heap = new Heap(compare);
heap.heapArray = heapArr;
return heap.top(n);
};
/**
* Return the `n` least valuable elements
* @param {Array} heapArr Array, should be a heap
* @param {number} n Max number of elements
* @param {Function} compare Optional compare function
* @return {any} Elements
*/
Heap.heapbottom = function (heapArr, n, compare) {
if (n === void 0) { n = 1; }
var heap = new Heap(compare);
heap.heapArray = heapArr;
return heap.bottom(n);
};
/*
Instance methods
*/
/**
* Adds an element to the heap. Aliases: `offer`.
* Same as: push(element)
* @param {any} element Element to be added
* @return {Boolean} true
*/
Heap.prototype.add = function (element) {
this._sortNodeUp(this.heapArray.push(element) - 1);
this._applyLimit();
return true;
};
/**
* Adds an array of elements to the heap.
* Similar as: push(element, element, ...).
* @param {Array} elements Elements to be added
* @return {Boolean} true
*/
Heap.prototype.addAll = function (elements) {
var i = this.length;
(_a = this.heapArray).push.apply(_a, elements);
for (var l = this.length; i < l; ++i) {
this._sortNodeUp(i);
}
this._applyLimit();
return true;
var _a;
};
/**
* Return the bottom (lowest value) N elements of the heap.
*
* @param {Number} n Number of elements.
* @return {Array} Array of length <= N.
*/
Heap.prototype.bottom = function (n) {
if (n === void 0) { n = 1; }
if (this.heapArray.length === 0 || n <= 0) {
// Nothing to do
return [];
}
else if (this.heapArray.length === 1) {
// Just the peek
return [this.heapArray[0]];
}
else if (n >= this.heapArray.length) {
// The whole peek
// Clone is needed due to the sort method (in-place) that would destroy the heap
var cloned = this.heapArray.slice(0);
cloned.sort(this._invertedCompare);
return cloned;
}
else {
// Some elements
var result = this._bottomN(n);
result.sort(this._invertedCompare);
return result;
}
};
/**
* Check if the heap is sorted, useful for testing purposes.
* @return {Undefined | Element} Returns an element if something wrong is found, otherwise it's undefined
*/
Heap.prototype.check = function () {
var _this = this;
return this.heapArray.find(function (el, j, arr) {
return !!_this.getChildrenOf(j).find(function (ch) { return _this.compare(el, ch) > 0; });
});
};
/**
* Remove all of the elements from this heap.
*/
Heap.prototype.clear = function () {
this.heapArray = [];
};
/**
* Clone this heap
* @return {Heap}
*/
Heap.prototype.clone = function () {
var cloned = new Heap(this.comparator());
cloned.heapArray = this.toArray();
cloned._limit = this._limit;
return cloned;
};
/**
* Returns the comparison function.
* @return {Function}
*/
Heap.prototype.comparator = function () {
return this.compare;
};
/**
* Returns true if this queue contains the specified element.
* @param {any} o Element to be found
* @param {Function} fn Optional comparison function, receives (element, needle)
* @return {Boolean}
*/
Heap.prototype.contains = function (o, fn) {
if (fn === void 0) { fn = Heap.defaultIsEqual; }
return this.heapArray.findIndex(function (el) { return fn(el, o); }) >= 0;
};
/**
* Initialise a heap, sorting nodes
* @param {Array} array Optional initial state array
*/
Heap.prototype.init = function (array) {
if (array) {
this.heapArray = array.slice(0);
}
for (var i = Math.floor(this.heapArray.length); i >= 0; --i) {
this._sortNodeDown(i);
}
this._applyLimit();
};
/**
* Test if the heap has no elements.
* @return {Boolean} True if no elements on the heap
*/
Heap.prototype.isEmpty = function () {
return this.length === 0;
};
/**
* Get the leafs of the tree (no children nodes)
*/
Heap.prototype.leafs = function () {
if (this.heapArray.length === 0) {
return [];
}
var pi = Heap.getParentIndexOf(this.heapArray.length - 1);
return this.heapArray.slice(pi + 1);
};
Object.defineProperty(Heap.prototype, "length", {
/**
* Length of the heap.
* @return {Number}
*/
get: function () {
return this.heapArray.length;
},
enumerable: true,
configurable: true
});
Object.defineProperty(Heap.prototype, "limit", {
/**
* Get length limit of the heap.
* @return {Number}
*/
get: function () {
return this._limit;
},
/**
* Set length limit of the heap.
* @return {Number}
*/
set: function (_l) {
this._limit = _l;
this._applyLimit();
},
enumerable: true,
configurable: true
});
/**
* Top node. Aliases: `element`.
* Same as: `top(1)[0]`
* @return {any} Top node
*/
Heap.prototype.peek = function () {
return this.heapArray[0];
};
/**
* Extract the top node (root). Aliases: `poll`.
* @return {any} Extracted top node, undefined if empty
*/
Heap.prototype.pop = function () {
var pop = this.heapArray.pop();
if (this.length > 0 && pop !== undefined) {
return this.replace(pop);
}
return pop;
};
/**
* Pushes element(s) to the heap.
* @param {...any} elements Elements to insert
* @return {Boolean} True if elements are present
*/
Heap.prototype.push = function () {
var elements = [];
for (var _i = 0; _i < arguments.length; _i++) {
elements[_i] = arguments[_i];
}
if (elements.length < 1) {
return false;
}
else if (elements.length === 1) {
return this.add(elements[0]);
}
else {
return this.addAll(elements);
}
};
/**
* Same as push & pop in sequence, but faster
* @param {any} element Element to insert
* @return {any} Extracted top node
*/
Heap.prototype.pushpop = function (element) {
if (this.compare(this.heapArray[0], element) < 0) {
_a = [this.heapArray[0], element], element = _a[0], this.heapArray[0] = _a[1];
this._sortNodeDown(0);
}
return element;
var _a;
};
/**
* Remove an element from the heap.
* @param {any} o Element to be found
* @param {Function} fn Optional function to compare
* @return {Boolean} True if the heap was modified
*/
Heap.prototype.remove = function (o, fn) {
if (fn === void 0) { fn = Heap.defaultIsEqual; }
if (this.length > 0) {
if (o === undefined) {
this.pop();
return true;
}
else {
var idx = this.heapArray.findIndex(function (el) { return fn(el, o); });
if (idx >= 0) {
if (idx === 0) {
this.pop();
}
else if (idx === this.length - 1) {
this.heapArray.pop();
}
else {
this.heapArray.splice(idx, 1, this.heapArray.pop());
this._sortNodeUp(idx);
this._sortNodeDown(idx);
}
return true;
}
}
}
return false;
};
/**
* Pop the current peek value, and add the new item.
* @param {any} element Element to replace peek
* @return {any} Old peek
*/
Heap.prototype.replace = function (element) {
var peek = this.heapArray[0];
this.heapArray[0] = element;
this._sortNodeDown(0);
return peek;
};
/**
* Size of the heap
* @return {Number}
*/
Heap.prototype.size = function () {
return this.length;
};
/**
* Return the top (highest value) N elements of the heap.
*
* @param {Number} n Number of elements.
* @return {Array} Array of length <= N.
*/
Heap.prototype.top = function (n) {
if (n === void 0) { n = 1; }
if (this.heapArray.length === 0 || n <= 0) {
// Nothing to do
return [];
}
else if (this.heapArray.length === 1 || n === 1) {
// Just the peek
return [this.heapArray[0]];
}
else if (n >= this.heapArray.length) {
// The whole peek
// Clone is needed due to the sort method (in-place) that would destroy the heap
var cloned = this.heapArray.slice(0);
cloned.sort(this.compare);
return cloned;
}
else {
// Some elements
var result = this._topN(n);
result.sort(this.compare);
return result;
}
};
/**
* Clone the heap's internal array
* @return {Array}
*/
Heap.prototype.toArray = function () {
return this.heapArray.slice(0);
};
/**
* String output, call to Array.prototype.toString()
* @return {String}
*/
Heap.prototype.toString = function () {
return this.heapArray.toString();
};
/**
* Get the element at the given index.
* @param {Number} i Index to get
* @return {any} Element at that index
*/
Heap.prototype.get = function (i) {
return this.heapArray[i];
};
/**
* Get the elements of these node's children
* @param {Number} idx Node index
* @return {Array(any)} Children elements
*/
Heap.prototype.getChildrenOf = function (idx) {
var _this = this;
return Heap.getChildrenIndexOf(idx)
.map(function (i) { return _this.heapArray[i]; })
.filter(function (e) { return e !== undefined; });
};
/**
* Get the element of this node's parent
* @param {Number} idx Node index
* @return {any} Parent element
*/
Heap.prototype.getParentOf = function (idx) {
var pi = Heap.getParentIndexOf(idx);
return this.heapArray[pi];
};
/**
* Limit heap size if needed
*/
Heap.prototype._applyLimit = function () {
if (this._limit && this._limit < this.heapArray.length) {
var rm = this.heapArray.length - this._limit;
// It's much faster than splice
while (rm) {
this.heapArray.pop();
--rm;
}
}
};
/**
* Return the bottom (lowest value) N elements of the heap, without corner cases, unsorted
*
* @param {Number} n Number of elements.
* @return {Array} Array of length <= N.
*/
Heap.prototype._bottomN = function (n) {
// Use an inverted heap
var bottomHeap = new Heap(this.compare);
bottomHeap.limit = n;
bottomHeap.init(this.heapArray.slice(-n));
var startAt = this.heapArray.length - 1 - n;
var parentStartAt = Heap.getParentIndexOf(startAt);
var indices = [];
for (var i = startAt; i > parentStartAt; --i) {
indices.push(i);
}
var arr = this.heapArray;
while (indices.length) {
var i = indices.shift();
if (this.compare(arr[i], bottomHeap.peek()) > 0) {
bottomHeap.replace(arr[i]);
if (i % 2) {
indices.push(Heap.getParentIndexOf(i));
}
}
}
return bottomHeap.toArray();
};
/**
* Move a node to a new index, switching places
* @param {Number} j First node index
* @param {Number} k Another node index
*/
Heap.prototype._moveNode = function (j, k) {
_a = [
this.heapArray[k],
this.heapArray[j]
], this.heapArray[j] = _a[0], this.heapArray[k] = _a[1];
var _a;
};
/**
* Move a node down the tree (to the leaves) to find a place where the heap is sorted.
* @param {Number} i Index of the node
*/
Heap.prototype._sortNodeDown = function (i) {
var _this = this;
var moveIt = i < this.heapArray.length - 1;
var moved = false;
var self = this.heapArray[i];
var getPotentialParent = function (best, j) {
if (_this.compare(_this.heapArray[j], _this.heapArray[best]) < 0) {
best = j;
}
return best;
};
while (moveIt) {
var childrenIdx = Heap.getChildrenIndexOf(i);
var bestChildIndex = childrenIdx.reduce(getPotentialParent, childrenIdx[0]);
var bestChild = this.heapArray[bestChildIndex];
if (typeof bestChild !== "undefined" &&
this.compare(self, bestChild) > 0) {
this._moveNode(i, bestChildIndex);
i = bestChildIndex;
moved = true;
}
else {
moveIt = false;
}
}
return moved;
};
/**
* Move a node up the tree (to the root) to find a place where the heap is sorted.
* @param {Number} i Index of the node
*/
Heap.prototype._sortNodeUp = function (i) {
var moveIt = i > 0;
var moved = false;
while (moveIt) {
var pi = Heap.getParentIndexOf(i);
if (pi >= 0 && this.compare(this.heapArray[pi], this.heapArray[i]) > 0) {
this._moveNode(i, pi);
i = pi;
moved = true;
}
else {
moveIt = false;
}
}
return moved;
};
/**
* Return the top (highest value) N elements of the heap, without corner cases, unsorted
*
* @param {Number} n Number of elements.
* @return {Array} Array of length <= N.
*/
Heap.prototype._topN = function (n) {
// Use an inverted heap
var topHeap = new Heap(this._invertedCompare);
topHeap.limit = n;
var indices = [0];
var arr = this.heapArray;
while (indices.length) {
var i = indices.shift();
if (i < arr.length) {
if (topHeap.length < n) {
topHeap.push(arr[i]);
indices.push.apply(indices, Heap.getChildrenIndexOf(i));
}
else if (this.compare(arr[i], topHeap.peek()) <= 0) {
topHeap.replace(arr[i]);
indices.push.apply(indices, Heap.getChildrenIndexOf(i));
}
}
}
return topHeap.toArray();
};
return Heap;
}());