/* * OpenSeadragon - Queue * * Copyright (C) 2023 OpenSeadragon contributors (modified) * Copyright (C) Google Inc., The Closure Library Authors. * https://github.com/google/closure-library * * SPDX-License-Identifier: Apache-2.0 */ (function($) { /** * @class PriorityQueue * @classdesc Fast priority queue. Implemented as a Heap. */ $.PriorityQueue = class { /** * @param {?OpenSeadragon.PriorityQueue} optHeap Optional Heap or * Object to initialize heap with. */ constructor(optHeap = undefined) { /** * The nodes of the heap. * * This is a densely packed array containing all nodes of the heap, using * the standard flat representation of a tree as an array (i.e. element [0] * at the top, with [1] and [2] as the second row, [3] through [6] as the * third, etc). Thus, the children of element `i` are `2i+1` and `2i+2`, and * the parent of element `i` is `⌊(i-1)/2⌋`. * * The only invariant is that children's keys must be greater than parents'. * * @private @const {!Array} */ this.nodes_ = []; if (optHeap) { this.insertAll(optHeap); } } /** * Insert the given value into the heap with the given key. * @param {K} key The key. * @param {V} value The value. */ insert(key, value) { this.insertNode(new Node(key, value)); } /** * Insert node item. * @param node */ insertNode(node) { const nodes = this.nodes_; node.index = nodes.length; nodes.push(node); this.moveUp_(node.index); } /** * Adds multiple key-value pairs from another Heap or Object * @param {?OpenSeadragon.PriorityQueue} heap Object containing the data to add. */ insertAll(heap) { let keys, values; if (heap instanceof $.PriorityQueue) { keys = heap.getKeys(); values = heap.getValues(); // If it is a heap and the current heap is empty, I can rely on the fact // that the keys/values are in the correct order to put in the underlying // structure. if (this.getCount() <= 0) { const nodes = this.nodes_; for (let i = 0; i < keys.length; i++) { const node = new Node(keys[i], values[i]); node.index = nodes.length; nodes.push(node); } return; } } else { throw "insertAll supports only OpenSeadragon.PriorityQueue object!"; } for (let i = 0; i < keys.length; i++) { this.insert(keys[i], values[i]); } } /** * Retrieves and removes the root value of this heap. * @return {Node} The root node item removed from the root of the heap. Returns * undefined if the heap is empty. */ remove() { const nodes = this.nodes_; const count = nodes.length; const rootNode = nodes[0]; if (count <= 0) { return undefined; } else if (count == 1) { // eslint-disable-line nodes.length = 0; } else { nodes[0] = nodes.pop(); if (nodes[0]) { nodes[0].index = 0; } this.moveDown_(0); } if (rootNode) { delete rootNode.index; } return rootNode; } /** * Retrieves but does not remove the root value of this heap. * @return {V} The value at the root of the heap. Returns * undefined if the heap is empty. */ peek() { const nodes = this.nodes_; if (nodes.length == 0) { // eslint-disable-line return undefined; } return nodes[0].value; } /** * Retrieves but does not remove the key of the root node of this heap. * @return {string} The key at the root of the heap. Returns undefined if the * heap is empty. */ peekKey() { return this.nodes_[0] && this.nodes_[0].key; } /** * Move the node up in hierarchy * @param {Node} node the node * @param {K} key new ley, must be smaller than current key */ decreaseKey(node, key) { if (node.index === undefined) { node.key = key; this.insertNode(node); } else { node.key = key; this.moveUp_(node.index); } } /** * Moves the node at the given index down to its proper place in the heap. * @param {number} index The index of the node to move down. * @private */ moveDown_(index) { const nodes = this.nodes_; const count = nodes.length; // Save the node being moved down. const node = nodes[index]; // While the current node has a child. while (index < (count >> 1)) { const leftChildIndex = this.getLeftChildIndex_(index); const rightChildIndex = this.getRightChildIndex_(index); // Determine the index of the smaller child. const smallerChildIndex = rightChildIndex < count && nodes[rightChildIndex].key < nodes[leftChildIndex].key ? rightChildIndex : leftChildIndex; // If the node being moved down is smaller than its children, the node // has found the correct index it should be at. if (nodes[smallerChildIndex].key > node.key) { break; } // If not, then take the smaller child as the current node. nodes[index] = nodes[smallerChildIndex]; nodes[index].index = index; index = smallerChildIndex; } nodes[index] = node; if (node) { node.index = index; } } /** * Moves the node at the given index up to its proper place in the heap. * @param {number} index The index of the node to move up. * @private */ moveUp_(index) { const nodes = this.nodes_; const node = nodes[index]; // While the node being moved up is not at the root. while (index > 0) { // If the parent is greater than the node being moved up, move the parent // down. const parentIndex = this.getParentIndex_(index); if (nodes[parentIndex].key > node.key) { nodes[index] = nodes[parentIndex]; nodes[index].index = index; index = parentIndex; } else { break; } } nodes[index] = node; if (node) { node.index = index; } } /** * 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; } /** * Gets the values of the heap. * @return {!Array<*>} The values in the heap. */ getValues() { const nodes = this.nodes_; const rv = []; const l = nodes.length; for (let i = 0; i < l; i++) { rv.push(nodes[i].value); } return rv; } /** * Gets the keys of the heap. * @return {!Array} The keys in the heap. */ getKeys() { const nodes = this.nodes_; const rv = []; const l = nodes.length; for (let i = 0; i < l; i++) { rv.push(nodes[i].key); } return rv; } /** * Whether the heap contains the given value. * @param {V} val The value to check for. * @return {boolean} Whether the heap contains the value. */ containsValue(val) { return this.nodes_.some((node) => node.value == val); // eslint-disable-line } /** * Whether the heap contains the given key. * @param {string} key The key to check for. * @return {boolean} Whether the heap contains the key. */ containsKey(key) { return this.nodes_.some((node) => node.value == key); // eslint-disable-line } /** * Clones a heap and returns a new heap * @return {!OpenSeadragon.PriorityQueue} A new Heap with the same key-value pairs. */ clone() { return new $.PriorityQueue(this); } /** * The number of key-value pairs in the map * @return {number} The number of pairs. */ getCount() { return this.nodes_.length; } /** * Returns true if this heap contains no elements. * @return {boolean} Whether this heap contains no elements. */ isEmpty() { return this.nodes_.length === 0; } /** * Removes all elements from the heap. */ clear() { this.nodes_.length = 0; } }; $.PriorityQueue.Node = class { constructor(key, value) { /** * The key. * @private {K} */ this.key = key; /** * The value. * @private {V} */ this.value = value; /** * The node index value. Updated in the heap. * @type {number} * @private */ this.index = 0; } clone() { return new Node(this.key, this.value); } }; }(OpenSeadragon));