273 lines
7.6 KiB
JavaScript
273 lines
7.6 KiB
JavaScript
|
|
'use strict';
|
|
|
|
var defaultcomparator = function (a, b) {
|
|
return a < b;
|
|
};
|
|
|
|
// the provided comparator function should take a, b and return *true* when a < b
|
|
function FastPriorityQueue(comparator) {
|
|
if (!(this instanceof FastPriorityQueue)) return new FastPriorityQueue(comparator);
|
|
this.array = [];
|
|
this.size = 0;
|
|
this.compare = comparator || defaultcomparator;
|
|
}
|
|
|
|
// copy the priority queue into another, and return it. Queue items are shallow-copied.
|
|
// Runs in `O(n)` time.
|
|
FastPriorityQueue.prototype.clone = function() {
|
|
var fpq = new FastPriorityQueue(this.compare);
|
|
fpq.size = this.size;
|
|
fpq.array = this.array.slice(0, this.size);
|
|
return fpq;
|
|
};
|
|
|
|
// Add an element into the queue
|
|
// runs in O(log n) time
|
|
FastPriorityQueue.prototype.add = function(value, obj) {
|
|
var i = this.size;
|
|
this.array[this.size] = { value, obj };
|
|
this.size += 1;
|
|
|
|
var p;
|
|
var ap;
|
|
|
|
while (i > 0) {
|
|
p = (i - 1) >> 1;
|
|
ap = this.array[p];
|
|
if (!this.compare(value, ap.value)) break;
|
|
|
|
this.array[i] = ap;
|
|
i = p;
|
|
};
|
|
|
|
this.array[i] = { value, obj };
|
|
};
|
|
|
|
// replace the content of the heap by provided array and "heapify it"
|
|
FastPriorityQueue.prototype.heapify = function(arr) {
|
|
this.array = arr;
|
|
this.size = arr.length;
|
|
var i;
|
|
for (i = this.size >> 1; i >= 0; i--) {
|
|
this._percolateDown(i);
|
|
}
|
|
};
|
|
|
|
// for internal use
|
|
FastPriorityQueue.prototype._percolateUp = function(i, force) {
|
|
var myval = this.array[i];
|
|
var p;
|
|
var ap;
|
|
while (i > 0) {
|
|
p = (i - 1) >> 1;
|
|
ap = this.array[p];
|
|
// force will skip the compare
|
|
if (!force && !this.compare(myval.value, ap.value)) {
|
|
break;
|
|
}
|
|
this.array[i] = ap;
|
|
i = p;
|
|
}
|
|
this.array[i] = myval;
|
|
};
|
|
|
|
// for internal use
|
|
FastPriorityQueue.prototype._percolateDown = function(i) {
|
|
var size = this.size;
|
|
var hsize = this.size >>> 1;
|
|
var ai = this.array[i];
|
|
var l;
|
|
var r;
|
|
var bestc;
|
|
while (i < hsize) {
|
|
l = (i << 1) + 1;
|
|
r = l + 1;
|
|
bestc = this.array[l];
|
|
if (r < size) {
|
|
if (this.compare(this.array[r].value, bestc.value)) {
|
|
l = r;
|
|
bestc = this.array[r];
|
|
}
|
|
}
|
|
if (!this.compare(bestc.value, ai.value)) {
|
|
break;
|
|
}
|
|
this.array[i] = bestc;
|
|
i = l;
|
|
}
|
|
this.array[i] = ai;
|
|
};
|
|
|
|
// internal
|
|
// _removeAt(index) will remove the item at the given index from the queue,
|
|
// retaining balance. returns the removed item, or undefined if nothing is removed.
|
|
FastPriorityQueue.prototype._removeAt = function(index) {
|
|
if (index > this.size - 1 || index < 0) return undefined;
|
|
|
|
// impl1:
|
|
//this.array.splice(index, 1);
|
|
//this.heapify(this.array);
|
|
// impl2:
|
|
this._percolateUp(index, true);
|
|
return this.poll();
|
|
};
|
|
|
|
// remove(myval) will remove an item matching the provided value from the
|
|
// queue, checked for equality by using the queue's comparator.
|
|
// return true if removed, false otherwise.
|
|
FastPriorityQueue.prototype.remove = function(myval) {
|
|
for (var i = 0; i < this.size; i++) {
|
|
if (!this.compare(this.array[i], myval) && !this.compare(myval, this.array[i])) {
|
|
// items match, comparator returns false both ways, remove item
|
|
this._removeAt(i);
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
};
|
|
|
|
// removeOne(callback) will execute the callback function for each item of the queue
|
|
// and will remove the first item for which the callback will return true.
|
|
// return the removed item, or undefined if nothing is removed.
|
|
FastPriorityQueue.prototype.removeOne = function(callback) {
|
|
if (typeof callback !== "function") {
|
|
return undefined;
|
|
}
|
|
for (var i = 0; i < this.size; i++) {
|
|
if (callback(this.array[i])) {
|
|
return this._removeAt(i);
|
|
}
|
|
}
|
|
};
|
|
|
|
// remove(callback[, limit]) will execute the callback function for each item of
|
|
// the queue and will remove each item for which the callback returns true, up to
|
|
// a max limit of removed items if specified or no limit if unspecified.
|
|
// return an array containing the removed items.
|
|
// The callback function should be a pure function.
|
|
FastPriorityQueue.prototype.removeMany = function(callback, limit) {
|
|
// Skip unnecessary processing for edge cases
|
|
if (typeof callback !== "function" || this.size < 1) {
|
|
return [];
|
|
}
|
|
limit = limit ? Math.min(limit, this.size) : this.size;
|
|
|
|
// Prepare the results container to hold up to the results limit
|
|
var resultSize = 0;
|
|
var result = new Array(limit);
|
|
|
|
// Prepare a temporary array to hold items we'll traverse through and need to keep
|
|
var tmpSize = 0;
|
|
var tmp = new Array(this.size);
|
|
|
|
while (resultSize < limit && !this.isEmpty()) {
|
|
// Dequeue items into either the results or our temporary array
|
|
var item = this.poll();
|
|
if (callback(item)) {
|
|
result[resultSize++] = item;
|
|
} else {
|
|
tmp[tmpSize++] = item;
|
|
}
|
|
}
|
|
// Update the result array with the exact number of results
|
|
result.length = resultSize;
|
|
|
|
// Re-add all the items we can keep
|
|
var i = 0;
|
|
while (i < tmpSize) {
|
|
const elem = tmp[i++];
|
|
this.add(elem.value, elem.obj);
|
|
}
|
|
|
|
return result;
|
|
};
|
|
|
|
// Look at the top of the queue (one of the smallest elements) without removing it
|
|
// executes in constant time
|
|
//
|
|
// Calling peek on an empty priority queue returns
|
|
// the "undefined" value.
|
|
// https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/undefined
|
|
//
|
|
FastPriorityQueue.prototype.peek = function() {
|
|
if (this.size == 0) return undefined;
|
|
return this.array[0];
|
|
};
|
|
|
|
// remove the element on top of the heap (one of the smallest elements)
|
|
// runs in logarithmic time
|
|
//
|
|
// If the priority queue is empty, the function returns the
|
|
// "undefined" value.
|
|
// https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/undefined
|
|
//
|
|
// For long-running and large priority queues, or priority queues
|
|
// storing large objects, you may want to call the trim function
|
|
// at strategic times to recover allocated memory.
|
|
FastPriorityQueue.prototype.poll = function() {
|
|
if (this.size == 0) return undefined;
|
|
var ans = this.array[0];
|
|
|
|
if (this.size > 1) {
|
|
this.array[0] = this.array[--this.size];
|
|
this._percolateDown(0);
|
|
} else {
|
|
this.size -= 1;
|
|
}
|
|
return ans;
|
|
};
|
|
|
|
// This function adds the provided value to the heap, while removing
|
|
// and returning one of the smallest elements (like poll). The size of the queue
|
|
// thus remains unchanged.
|
|
FastPriorityQueue.prototype.replaceTop = function(myval) {
|
|
if (this.size == 0) return undefined;
|
|
var ans = this.array[0];
|
|
this.array[0] = myval;
|
|
this._percolateDown(0);
|
|
return ans;
|
|
};
|
|
|
|
// recover unused memory (for long-running priority queues)
|
|
FastPriorityQueue.prototype.trim = function() {
|
|
this.array = this.array.slice(0, this.size);
|
|
};
|
|
|
|
// Check whether the heap is empty
|
|
FastPriorityQueue.prototype.isEmpty = function() {
|
|
return this.size === 0;
|
|
};
|
|
|
|
FastPriorityQueue.prototype.forEach = function(callback) {
|
|
if (this.isEmpty() || typeof callback != 'function') return;
|
|
var i = 0;
|
|
var fpq = this.clone();
|
|
let isRunning = true;
|
|
|
|
while (!fpq.isEmpty() && isRunning) {
|
|
isRunning = callback(fpq.poll(), i++);
|
|
}
|
|
};
|
|
|
|
// return the k 'smallest' elements of the queue as an array,
|
|
// runs in O(k log k) time, the elements are not removed
|
|
// from the priority queue.
|
|
FastPriorityQueue.prototype.kSmallest = function(k) {
|
|
if ((this.size == 0) || (k<=0)) return [];
|
|
k = Math.min(this.size, k);
|
|
const newSize = Math.min(this.size, (2 ** (k - 1)) + 1);
|
|
if (newSize < 2) { return [this.peek()] }
|
|
|
|
const fpq = new FastPriorityQueue(this.compare);
|
|
fpq.size = newSize;
|
|
fpq.array = this.array.slice(0, newSize);
|
|
|
|
const smallest = new Array(k);
|
|
for (let i = 0; i < k; i++) {
|
|
smallest[i] = fpq.poll();
|
|
}
|
|
return smallest;
|
|
}
|
|
|
|
module.exports = FastPriorityQueue; |