/** * @module ol/structs/PriorityQueue */ import {assert} from '../asserts.js'; import {clear} from '../obj.js'; /** * @type {number} */ export const DROP = Infinity; /** * @classdesc * Priority queue. * * The implementation is inspired from the Closure Library's Heap class and * Python's heapq module. * * See https://github.com/google/closure-library/blob/master/closure/goog/structs/heap.js * and https://hg.python.org/cpython/file/2.7/Lib/heapq.py. * * @template T */ class PriorityQueue { /** * @param {function(T): number} priorityFunction Priority function. * @param {function(T): string} keyFunction Key function. */ constructor(priorityFunction, keyFunction) { /** * @type {function(T): number} * @private */ this.priorityFunction_ = priorityFunction; /** * @type {function(T): string} * @private */ this.keyFunction_ = keyFunction; /** * @type {Array} * @private */ this.elements_ = []; /** * @type {Array} * @private */ this.priorities_ = []; /** * @type {!Object} * @private */ this.queuedElements_ = {}; } /** * FIXME empty description for jsdoc */ clear() { this.elements_.length = 0; this.priorities_.length = 0; clear(this.queuedElements_); } /** * Remove and return the highest-priority element. O(log N). * @return {T} Element. */ dequeue() { const elements = this.elements_; const priorities = this.priorities_; const element = elements[0]; if (elements.length == 1) { elements.length = 0; priorities.length = 0; } else { elements[0] = elements.pop(); priorities[0] = priorities.pop(); this.siftUp_(0); } const elementKey = this.keyFunction_(element); delete this.queuedElements_[elementKey]; return element; } /** * Enqueue an element. O(log N). * @param {T} element Element. * @return {boolean} The element was added to the queue. */ enqueue(element) { assert(!(this.keyFunction_(element) in this.queuedElements_), 31); // Tried to enqueue an `element` that was already added to the queue const priority = this.priorityFunction_(element); if (priority != DROP) { this.elements_.push(element); this.priorities_.push(priority); this.queuedElements_[this.keyFunction_(element)] = true; this.siftDown_(0, this.elements_.length - 1); return true; } return false; } /** * @return {number} Count. */ getCount() { return this.elements_.length; } /** * Gets the index of the left child of the node at the given index. * @param {number} index The index of the node to get the left child for. * @return {number} The index of the left child. * @private */ getLeftChildIndex_(index) { return index * 2 + 1; } /** * Gets the index of the right child of the node at the given index. * @param {number} index The index of the node to get the right child for. * @return {number} The index of the right child. * @private */ getRightChildIndex_(index) { return index * 2 + 2; } /** * Gets the index of the parent of the node at the given index. * @param {number} index The index of the node to get the parent for. * @return {number} The index of the parent. * @private */ getParentIndex_(index) { return (index - 1) >> 1; } /** * Make this a heap. O(N). * @private */ heapify_() { let i; for (i = (this.elements_.length >> 1) - 1; i >= 0; i--) { this.siftUp_(i); } } /** * @return {boolean} Is empty. */ isEmpty() { return this.elements_.length === 0; } /** * @param {string} key Key. * @return {boolean} Is key queued. */ isKeyQueued(key) { return key in this.queuedElements_; } /** * @param {T} element Element. * @return {boolean} Is queued. */ isQueued(element) { return this.isKeyQueued(this.keyFunction_(element)); } /** * @param {number} index The index of the node to move down. * @private */ siftUp_(index) { const elements = this.elements_; const priorities = this.priorities_; const count = elements.length; const element = elements[index]; const priority = priorities[index]; const startIndex = index; while (index < count >> 1) { const lIndex = this.getLeftChildIndex_(index); const rIndex = this.getRightChildIndex_(index); const smallerChildIndex = rIndex < count && priorities[rIndex] < priorities[lIndex] ? rIndex : lIndex; elements[index] = elements[smallerChildIndex]; priorities[index] = priorities[smallerChildIndex]; index = smallerChildIndex; } elements[index] = element; priorities[index] = priority; this.siftDown_(startIndex, index); } /** * @param {number} startIndex The index of the root. * @param {number} index The index of the node to move up. * @private */ siftDown_(startIndex, index) { const elements = this.elements_; const priorities = this.priorities_; const element = elements[index]; const priority = priorities[index]; while (index > startIndex) { const parentIndex = this.getParentIndex_(index); if (priorities[parentIndex] > priority) { elements[index] = elements[parentIndex]; priorities[index] = priorities[parentIndex]; index = parentIndex; } else { break; } } elements[index] = element; priorities[index] = priority; } /** * FIXME empty description for jsdoc */ reprioritize() { const priorityFunction = this.priorityFunction_; const elements = this.elements_; const priorities = this.priorities_; let index = 0; const n = elements.length; let element, i, priority; for (i = 0; i < n; ++i) { element = elements[i]; priority = priorityFunction(element); if (priority == DROP) { delete this.queuedElements_[this.keyFunction_(element)]; } else { priorities[index] = priority; elements[index++] = element; } } elements.length = index; priorities.length = index; this.heapify_(); } } export default PriorityQueue;