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