/* * (c) Copyright Ascensio System SIA 2010-2024 * * This program is a free software product. You can redistribute it and/or * modify it under the terms of the GNU Affero General Public License (AGPL) * version 3 as published by the Free Software Foundation. In accordance with * Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect * that Ascensio System SIA expressly excludes the warranty of non-infringement * of any third-party rights. * * This program is distributed WITHOUT ANY WARRANTY; without even the implied * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html * * You can contact Ascensio System SIA at 20A-6 Ernesta Birznieka-Upish * street, Riga, Latvia, EU, LV-1050. * * The interactive user interfaces in modified source and object code versions * of the Program must display Appropriate Legal Notices, as required under * Section 5 of the GNU AGPL version 3. * * Pursuant to Section 7(b) of the License you must retain the original Product * logo when distributing the program. Pursuant to Section 7(e) we decline to * grant you any rights under trademark law for use of our trademarks. * * All the Product's GUI elements, including illustrations and icon sets, as * well as technical writing content are licensed under the terms of the * Creative Commons Attribution-ShareAlike 4.0 International. See the License * terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode * */ "use strict"; (function(window) { // An augmented AVL Tree where each node maintains a list of records and their search intervals. // Record is composed of an interval and its underlying data, sent by a client. This allows the // interval tree to have the same interval inserted multiple times, as long its data is different. // Both insertion and deletion require O(log n) time. Searching requires O(k*logn) time, where `k` // is the number of intervals in the output list. var isSame = function shallowEqual(objA, objB, compare, compareContext) { var ret = compare ? compare.call(compareContext, objA, objB) : void 0; if (ret !== void 0) { return !!ret; } if (objA === objB) { return true; } if (typeof objA !== "object" || !objA || typeof objB !== "object" || !objB) { return false; } var keysA = Object.keys(objA); var keysB = Object.keys(objB); if (keysA.length !== keysB.length) { return false; } var bHasOwnProperty = Object.prototype.hasOwnProperty.bind(objB); // Test for A's keys different from B. for (var idx = 0; idx < keysA.length; idx++) { var key = keysA[idx]; if (!bHasOwnProperty(key)) { return false; } var valueA = objA[key]; var valueB = objB[key]; ret = compare ? compare.call(compareContext, valueA, valueB, key) : void 0; if (ret === false || (ret === void 0 && valueA !== valueB)) { return false; } } return true; }; function height(node) { if (node === undefined) { return -1; } else { return node.height; } } var Node = /** @class */ (function () { function Node(intervalTree, record) { this.intervalTree = intervalTree; this.records = []; this.height = 0; this.key = record.low; this.max = record.high; // Save the array of all records with the same key for this node this.records.push(record); } // Gets the highest record.high value for this node Node.prototype.getNodeHigh = function () { var high = this.records[0].high; for (var i = 1; i < this.records.length; i++) { if (this.records[i].high > high) { high = this.records[i].high; } } return high; }; // Updates height value of the node. Called during insertion, rebalance, removal Node.prototype.updateHeight = function () { this.height = Math.max(height(this.left), height(this.right)) + 1; }; // Updates the max value of all the parents after inserting into already existing node, as well as // removing the node completely or removing the record of an already existing node. Starts with // the parent of an affected node and bubbles up to root Node.prototype.updateMaxOfParents = function () { if (this === undefined) { return; } var thisHigh = this.getNodeHigh(); if (this.left !== undefined && this.right !== undefined) { this.max = Math.max(Math.max(this.left.max, this.right.max), thisHigh); } else if (this.left !== undefined && this.right === undefined) { this.max = Math.max(this.left.max, thisHigh); } else if (this.left === undefined && this.right !== undefined) { this.max = Math.max(this.right.max, thisHigh); } else { this.max = thisHigh; } if (this.parent) { this.parent.updateMaxOfParents(); } }; /* Left-Left case: z y / \ / \ y T4 Right Rotate (z) x z / \ - - - - - - - - -> / \ / \ x T3 T1 T2 T3 T4 / \ T1 T2 Left-Right case: z z x / \ / \ / \ y T4 Left Rotate (y) x T4 Right Rotate(z) y z / \ - - - - - - - - -> / \ - - - - - - - -> / \ / \ T1 x y T3 T1 T2 T3 T4 / \ / \ T2 T3 T1 T2 */ // Handles Left-Left case and Left-Right case after rebalancing AVL tree Node.prototype._updateMaxAfterRightRotate = function () { var parent = this.parent; var left = parent.left; // Update max of left sibling (x in first case, y in second) var thisParentLeftHigh = left.getNodeHigh(); if (left.left === undefined && left.right !== undefined) { left.max = Math.max(thisParentLeftHigh, left.right.max); } else if (left.left !== undefined && left.right === undefined) { left.max = Math.max(thisParentLeftHigh, left.left.max); } else if (left.left === undefined && left.right === undefined) { left.max = thisParentLeftHigh; } else { left.max = Math.max(Math.max(left.left.max, left.right.max), thisParentLeftHigh); } // Update max of itself (z) var thisHigh = this.getNodeHigh(); if (this.left === undefined && this.right !== undefined) { this.max = Math.max(thisHigh, this.right.max); } else if (this.left !== undefined && this.right === undefined) { this.max = Math.max(thisHigh, this.left.max); } else if (this.left === undefined && this.right === undefined) { this.max = thisHigh; } else { this.max = Math.max(Math.max(this.left.max, this.right.max), thisHigh); } // Update max of parent (y in first case, x in second) parent.max = Math.max(Math.max(parent.left.max, parent.right.max), parent.getNodeHigh()); }; /* Right-Right case: z y / \ / \ T1 y Left Rotate(z) z x / \ - - - - - - - -> / \ / \ T2 x T1 T2 T3 T4 / \ T3 T4 Right-Left case: z z x / \ / \ / \ T1 y Right Rotate (y) T1 x Left Rotate(z) z y / \ - - - - - - - - -> / \ - - - - - - - -> / \ / \ x T4 T2 y T1 T2 T3 T4 / \ / \ T2 T3 T3 T4 */ // Handles Right-Right case and Right-Left case in rebalancing AVL tree Node.prototype._updateMaxAfterLeftRotate = function () { var parent = this.parent; var right = parent.right; // Update max of right sibling (x in first case, y in second) var thisParentRightHigh = right.getNodeHigh(); if (right.left === undefined && right.right !== undefined) { right.max = Math.max(thisParentRightHigh, right.right.max); } else if (right.left !== undefined && right.right === undefined) { right.max = Math.max(thisParentRightHigh, right.left.max); } else if (right.left === undefined && right.right === undefined) { right.max = thisParentRightHigh; } else { right.max = Math.max(Math.max(right.left.max, right.right.max), thisParentRightHigh); } // Update max of itself (z) var thisHigh = this.getNodeHigh(); if (this.left === undefined && this.right !== undefined) { this.max = Math.max(thisHigh, this.right.max); } else if (this.left !== undefined && this.right === undefined) { this.max = Math.max(thisHigh, this.left.max); } else if (this.left === undefined && this.right === undefined) { this.max = thisHigh; } else { this.max = Math.max(Math.max(this.left.max, this.right.max), thisHigh); } // Update max of parent (y in first case, x in second) parent.max = Math.max(Math.max(parent.left.max, right.max), parent.getNodeHigh()); }; Node.prototype._leftRotate = function () { var rightChild = this.right; rightChild.parent = this.parent; if (rightChild.parent === undefined) { this.intervalTree.root = rightChild; } else { if (rightChild.parent.left === this) { rightChild.parent.left = rightChild; } else if (rightChild.parent.right === this) { rightChild.parent.right = rightChild; } } this.right = rightChild.left; if (this.right !== undefined) { this.right.parent = this; } rightChild.left = this; this.parent = rightChild; this.updateHeight(); rightChild.updateHeight(); }; Node.prototype._rightRotate = function () { var leftChild = this.left; leftChild.parent = this.parent; if (leftChild.parent === undefined) { this.intervalTree.root = leftChild; } else { if (leftChild.parent.left === this) { leftChild.parent.left = leftChild; } else if (leftChild.parent.right === this) { leftChild.parent.right = leftChild; } } this.left = leftChild.right; if (this.left !== undefined) { this.left.parent = this; } leftChild.right = this; this.parent = leftChild; this.updateHeight(); leftChild.updateHeight(); }; // Rebalances the tree if the height value between two nodes of the same parent is greater than // two. There are 4 cases that can happen which are outlined in the graphics above Node.prototype._rebalance = function () { if (height(this.left) >= 2 + height(this.right)) { var left = this.left; if (height(left.left) >= height(left.right)) { // Left-Left case this._rightRotate(); this._updateMaxAfterRightRotate(); } else { // Left-Right case left._leftRotate(); this._rightRotate(); this._updateMaxAfterRightRotate(); } } else if (height(this.right) >= 2 + height(this.left)) { var right = this.right; if (height(right.right) >= height(right.left)) { // Right-Right case this._leftRotate(); this._updateMaxAfterLeftRotate(); } else { // Right-Left case right._rightRotate(); this._leftRotate(); this._updateMaxAfterLeftRotate(); } } }; Node.prototype.insert = function (record) { if (record.low < this.key) { // Insert into left subtree if (this.left === undefined) { this.left = new Node(this.intervalTree, record); this.left.parent = this; } else { this.left.insert(record); } } else { // Insert into right subtree if (this.right === undefined) { this.right = new Node(this.intervalTree, record); this.right.parent = this; } else { this.right.insert(record); } } // Update the max value of this ancestor if needed if (this.max < record.high) { this.max = record.high; } // Update height of each node this.updateHeight(); // Rebalance the tree to ensure all operations are executed in O(logn) time. This is especially // important in searching, as the tree has a high chance of degenerating without the rebalancing this._rebalance(); }; Node.prototype._getOverlappingRecords = function (currentNode, low, high, output) { if (currentNode.key <= high && low <= currentNode.getNodeHigh()) { // Nodes are overlapping, check if individual records in the node are overlapping for (var i = 0; i < currentNode.records.length; i++) { if (currentNode.records[i].high >= low) { output.push(currentNode.records[i]); } } } }; Node.prototype.search = function (low, high, output) { // Don't search nodes that don't exist if (this === undefined) { return; } // If interval is to the right of the rightmost point of any interval in this node and all its // children, there won't be any matches if (low > this.max) { return; } // Search left children if (this.left !== undefined && this.left.max >= low) { this.left.search(low, high, output); } // Check this node this._getOverlappingRecords(this, low, high, output); // If interval is to the left of the start of this interval, then it can't be in any child to // the right if (high < this.key) { return; } // Otherwise, search right children if (this.right !== undefined) { this.right.search(low, high, output); } }; // Searches for any overlapping node Node.prototype.searchAny = function(low, high) { // Don't search nodes that don't exist if (this === undefined) { return; } // If interval is to the right of the rightmost point of any interval in this node and all its // children, there won't be any matches if (low > this.max) { return; } if (this.key <= high) { // Nodes are overlapping, check if individual records in the node are overlapping for (var i = 0; i < this.records.length; i++) { if (this.records[i].high >= low) { return this.records[i]; } } } // Search left children if (this.left !== undefined && this.left.max >= low) { return this.left.searchAny(low, high); } else if (this.right !== undefined && this.key <= high) { return this.right.searchAny(low, high); } }; // Searches for a node by a `key` value Node.prototype.searchExisting = function (low) { if (this === undefined) { return undefined; } if (this.key === low) { return this; } else if (low < this.key) { if (this.left !== undefined) { return this.left.searchExisting(low); } } else { if (this.right !== undefined) { return this.right.searchExisting(low); } } return undefined; }; // Returns the smallest node of the subtree Node.prototype._minValue = function () { if (this.left === undefined) { return this; } else { return this.left._minValue(); } }; Node.prototype.remove = function (node) { var parent = this.parent; if (node.key < this.key) { // Node to be removed is on the left side if (this.left !== undefined) { return this.left.remove(node); } else { return undefined; } } else if (node.key > this.key) { // Node to be removed is on the right side if (this.right !== undefined) { return this.right.remove(node); } else { return undefined; } } else { if (this.left !== undefined && this.right !== undefined) { // Node has two children var minValue = this.right._minValue(); this.key = minValue.key; this.records = minValue.records; return this.right.remove(this); } else if (parent.left === this) { // One child or no child case on left side if (this.right !== undefined) { parent.left = this.right; this.right.parent = parent; } else { parent.left = this.left; if (this.left !== undefined) { this.left.parent = parent; } } parent.updateMaxOfParents(); parent.updateHeight(); parent._rebalance(); return this; } else if (parent.right === this) { // One child or no child case on right side if (this.right !== undefined) { parent.right = this.right; this.right.parent = parent; } else { parent.right = this.left; if (this.left !== undefined) { this.left.parent = parent; } } parent.updateMaxOfParents(); parent.updateHeight(); parent._rebalance(); return this; } } }; return Node; }()); var NodeWithId = /** @class */ (function () { function NodeWithId(intervalTree, record) { this.intervalTree = intervalTree; this.records = {}; this.recordsCount = 0; this.height = 0; this.key = record.low; this.max = record.high; // Save the array of all records with the same key for this node this.records[record.id] = record; this.recordsCount = 1; } // Gets the highest record.high value for this node NodeWithId.prototype.getNodeHigh = function () { var high = Number.NEGATIVE_INFINITY; for (var i in this.records) { if (this.records.hasOwnProperty(i)) { high = Math.max(high, this.records[i].high); } } return high; }; // Updates height value of the node. Called during insertion, rebalance, removal NodeWithId.prototype.updateHeight = function () { this.height = Math.max(height(this.left), height(this.right)) + 1; }; // Updates the max value of all the parents after inserting into already existing node, as well as // removing the node completely or removing the record of an already existing node. Starts with // the parent of an affected node and bubbles up to root NodeWithId.prototype.updateMaxOfParents = function () { if (this === undefined) { return; } var thisHigh = this.getNodeHigh(); if (this.left !== undefined && this.right !== undefined) { this.max = Math.max(Math.max(this.left.max, this.right.max), thisHigh); } else if (this.left !== undefined && this.right === undefined) { this.max = Math.max(this.left.max, thisHigh); } else if (this.left === undefined && this.right !== undefined) { this.max = Math.max(this.right.max, thisHigh); } else { this.max = thisHigh; } if (this.parent) { this.parent.updateMaxOfParents(); } }; /* Left-Left case: z y / \ / \ y T4 Right Rotate (z) x z / \ - - - - - - - - -> / \ / \ x T3 T1 T2 T3 T4 / \ T1 T2 Left-Right case: z z x / \ / \ / \ y T4 Left Rotate (y) x T4 Right Rotate(z) y z / \ - - - - - - - - -> / \ - - - - - - - -> / \ / \ T1 x y T3 T1 T2 T3 T4 / \ / \ T2 T3 T1 T2 */ // Handles Left-Left case and Left-Right case after rebalancing AVL tree NodeWithId.prototype._updateMaxAfterRightRotate = function () { var parent = this.parent; var left = parent.left; // Update max of left sibling (x in first case, y in second) var thisParentLeftHigh = left.getNodeHigh(); if (left.left === undefined && left.right !== undefined) { left.max = Math.max(thisParentLeftHigh, left.right.max); } else if (left.left !== undefined && left.right === undefined) { left.max = Math.max(thisParentLeftHigh, left.left.max); } else if (left.left === undefined && left.right === undefined) { left.max = thisParentLeftHigh; } else { left.max = Math.max(Math.max(left.left.max, left.right.max), thisParentLeftHigh); } // Update max of itself (z) var thisHigh = this.getNodeHigh(); if (this.left === undefined && this.right !== undefined) { this.max = Math.max(thisHigh, this.right.max); } else if (this.left !== undefined && this.right === undefined) { this.max = Math.max(thisHigh, this.left.max); } else if (this.left === undefined && this.right === undefined) { this.max = thisHigh; } else { this.max = Math.max(Math.max(this.left.max, this.right.max), thisHigh); } // Update max of parent (y in first case, x in second) parent.max = Math.max(Math.max(parent.left.max, parent.right.max), parent.getNodeHigh()); }; /* Right-Right case: z y / \ / \ T1 y Left Rotate(z) z x / \ - - - - - - - -> / \ / \ T2 x T1 T2 T3 T4 / \ T3 T4 Right-Left case: z z x / \ / \ / \ T1 y Right Rotate (y) T1 x Left Rotate(z) z y / \ - - - - - - - - -> / \ - - - - - - - -> / \ / \ x T4 T2 y T1 T2 T3 T4 / \ / \ T2 T3 T3 T4 */ // Handles Right-Right case and Right-Left case in rebalancing AVL tree NodeWithId.prototype._updateMaxAfterLeftRotate = function () { var parent = this.parent; var right = parent.right; // Update max of right sibling (x in first case, y in second) var thisParentRightHigh = right.getNodeHigh(); if (right.left === undefined && right.right !== undefined) { right.max = Math.max(thisParentRightHigh, right.right.max); } else if (right.left !== undefined && right.right === undefined) { right.max = Math.max(thisParentRightHigh, right.left.max); } else if (right.left === undefined && right.right === undefined) { right.max = thisParentRightHigh; } else { right.max = Math.max(Math.max(right.left.max, right.right.max), thisParentRightHigh); } // Update max of itself (z) var thisHigh = this.getNodeHigh(); if (this.left === undefined && this.right !== undefined) { this.max = Math.max(thisHigh, this.right.max); } else if (this.left !== undefined && this.right === undefined) { this.max = Math.max(thisHigh, this.left.max); } else if (this.left === undefined && this.right === undefined) { this.max = thisHigh; } else { this.max = Math.max(Math.max(this.left.max, this.right.max), thisHigh); } // Update max of parent (y in first case, x in second) parent.max = Math.max(Math.max(parent.left.max, right.max), parent.getNodeHigh()); }; NodeWithId.prototype._leftRotate = function () { var rightChild = this.right; rightChild.parent = this.parent; if (rightChild.parent === undefined) { this.intervalTree.root = rightChild; } else { if (rightChild.parent.left === this) { rightChild.parent.left = rightChild; } else if (rightChild.parent.right === this) { rightChild.parent.right = rightChild; } } this.right = rightChild.left; if (this.right !== undefined) { this.right.parent = this; } rightChild.left = this; this.parent = rightChild; this.updateHeight(); rightChild.updateHeight(); }; NodeWithId.prototype._rightRotate = function () { var leftChild = this.left; leftChild.parent = this.parent; if (leftChild.parent === undefined) { this.intervalTree.root = leftChild; } else { if (leftChild.parent.left === this) { leftChild.parent.left = leftChild; } else if (leftChild.parent.right === this) { leftChild.parent.right = leftChild; } } this.left = leftChild.right; if (this.left !== undefined) { this.left.parent = this; } leftChild.right = this; this.parent = leftChild; this.updateHeight(); leftChild.updateHeight(); }; // Rebalances the tree if the height value between two nodes of the same parent is greater than // two. There are 4 cases that can happen which are outlined in the graphics above NodeWithId.prototype._rebalance = function () { if (height(this.left) >= 2 + height(this.right)) { var left = this.left; if (height(left.left) >= height(left.right)) { // Left-Left case this._rightRotate(); this._updateMaxAfterRightRotate(); } else { // Left-Right case left._leftRotate(); this._rightRotate(); this._updateMaxAfterRightRotate(); } } else if (height(this.right) >= 2 + height(this.left)) { var right = this.right; if (height(right.right) >= height(right.left)) { // Right-Right case this._leftRotate(); this._updateMaxAfterLeftRotate(); } else { // Right-Left case right._rightRotate(); this._leftRotate(); this._updateMaxAfterLeftRotate(); } } }; NodeWithId.prototype.insert = function (record) { if (record.low < this.key) { // Insert into left subtree if (this.left === undefined) { this.left = new NodeWithId(this.intervalTree, record); this.left.parent = this; } else { this.left.insert(record); } } else { // Insert into right subtree if (this.right === undefined) { this.right = new NodeWithId(this.intervalTree, record); this.right.parent = this; } else { this.right.insert(record); } } // Update the max value of this ancestor if needed if (this.max < record.high) { this.max = record.high; } // Update height of each node this.updateHeight(); // Rebalance the tree to ensure all operations are executed in O(logn) time. This is especially // important in searching, as the tree has a high chance of degenerating without the rebalancing this._rebalance(); }; NodeWithId.prototype._getOverlappingRecords = function (currentNode, low, high, output) { if (currentNode.key <= high && low <= currentNode.max) { // Nodes are overlapping, check if individual records in the node are overlapping for (var i in this.records) { if (this.records.hasOwnProperty(i)) { if (this.records[i].high >= low) { output.push(this.records[i]); } } } } }; NodeWithId.prototype.search = function (low, high, output) { // Don't search nodes that don't exist if (this === undefined) { return; } // If interval is to the right of the rightmost point of any interval in this node and all its // children, there won't be any matches if (low > this.max) { return; } // Search left children if (this.left !== undefined && this.left.max >= low) { this.left.search(low, high, output); } // Check this node this._getOverlappingRecords(this, low, high, output); // If interval is to the left of the start of this interval, then it can't be in any child to // the right if (high < this.key) { return; } // Otherwise, search right children if (this.right !== undefined) { this.right.search(low, high, output); } }; // Searches for any overlapping node NodeWithId.prototype.searchAny = function(low, high) { // Don't search nodes that don't exist if (this === undefined) { return; } // If interval is to the right of the rightmost point of any interval in this node and all its // children, there won't be any matches if (low > this.max) { return; } if (this.key <= high) { // Nodes are overlapping, check if individual records in the node are overlapping for (var i in this.records) { if (this.records.hasOwnProperty(i)) { if (this.records[i].high >= low) { return this.records[i]; } } } } // Search left children if (this.left !== undefined && this.left.max >= low) { return this.left.searchAny(low, high); } else if (this.right !== undefined && this.key <= high) { return this.right.searchAny(low, high); } }; // Searches for a node by a `key` value NodeWithId.prototype.searchExisting = function (low) { if (this === undefined) { return undefined; } if (this.key === low) { return this; } else if (low < this.key) { if (this.left !== undefined) { return this.left.searchExisting(low); } } else { if (this.right !== undefined) { return this.right.searchExisting(low); } } return undefined; }; // Returns the smallest node of the subtree NodeWithId.prototype._minValue = function () { if (this.left === undefined) { return this; } else { return this.left._minValue(); } }; NodeWithId.prototype.remove = function (node) { var parent = this.parent; if (node.key < this.key) { // NodeWithId to be removed is on the left side if (this.left !== undefined) { return this.left.remove(node); } else { return undefined; } } else if (node.key > this.key) { // NodeWithId to be removed is on the right side if (this.right !== undefined) { return this.right.remove(node); } else { return undefined; } } else { if (this.left !== undefined && this.right !== undefined) { // NodeWithId has two children var minValue = this.right._minValue(); this.key = minValue.key; this.records = minValue.records; this.recordsCount = minValue.recordsCount; return this.right.remove(this); } else if (parent.left === this) { // One child or no child case on left side if (this.right !== undefined) { parent.left = this.right; this.right.parent = parent; } else { parent.left = this.left; if (this.left !== undefined) { this.left.parent = parent; } } parent.updateMaxOfParents(); parent.updateHeight(); parent._rebalance(); return this; } else if (parent.right === this) { // One child or no child case on right side if (this.right !== undefined) { parent.right = this.right; this.right.parent = parent; } else { parent.right = this.left; if (this.left !== undefined) { this.left.parent = parent; } } parent.updateMaxOfParents(); parent.updateHeight(); parent._rebalance(); return this; } } }; return NodeWithId; }()); var IntervalTree = /** @class */ (function () { function IntervalTree() { this.count = 0; } IntervalTree.prototype.insert = function (record) { if (record.low > record.high) { throw new Error('`low` value must be lower or equal to `high` value'); } if (this.root === undefined) { // Base case: Tree is empty, new node becomes root this.root = new Node(this, record); this.count++; return true; } else { // Otherwise, check if node already exists with the same key var node = this.root.searchExisting(record.low); if (node !== undefined) { // Check the records in this node if there already is the one with same low, high, data for (var i = 0; i < node.records.length; i++) { if (isSame(node.records[i], record)) { // This record is same as the one we're trying to insert; return false to indicate // nothing has been inserted return false; } } // Add the record to the node node.records.push(record); // Update max of the node and its parents if necessary if (record.high > node.max) { node.max = record.high; if (node.parent) { node.parent.updateMaxOfParents(); } } this.count++; return true; } else { // Node with this key doesn't already exist. Call insert function on root's node this.root.insert(record); this.count++; return true; } } }; IntervalTree.prototype.search = function (low, high, opt_output) { if (!opt_output) { opt_output = []; } if (this.root !== undefined) { this.root.search(low, high, opt_output); } return opt_output; }; IntervalTree.prototype.searchExisting = function (low, high) { if (this.root) { var node = this.root.searchExisting(low); if (node) { for (var i = 0; i < node.records.length; i++) { if (node.records[i].high === high) { return node.records[i]; } } } } }; IntervalTree.prototype.searchAny = function (low, high) { if (this.root) { return this.root.searchAny(low, high); } }; IntervalTree.prototype.remove = function (record) { if (this.root === undefined) { // Tree is empty; nothing to remove return false; } else { var node = this.root.searchExisting(record.low); if (node === undefined) { return false; } else if (node.records.length > 1) { var removedRecord = void 0; // Node with this key has 2 or more records. Find the one we need and remove it for (var i = 0; i < node.records.length; i++) { if (isSame(node.records[i], record)) { removedRecord = node.records[i]; node.records.splice(i, 1); break; } } if (removedRecord) { removedRecord = undefined; // Update max of that node and its parents if necessary if (record.high === node.max) { var nodeHigh = node.getNodeHigh(); if (node.left !== undefined && node.right !== undefined) { node.max = Math.max(Math.max(node.left.max, node.right.max), nodeHigh); } else if (node.left !== undefined && node.right === undefined) { node.max = Math.max(node.left.max, nodeHigh); } else if (node.left === undefined && node.right !== undefined) { node.max = Math.max(node.right.max, nodeHigh); } else { node.max = nodeHigh; } if (node.parent) { node.parent.updateMaxOfParents(); } } this.count--; return true; } else { return false; } } else if (node.records.length === 1) { // Node with this key has only 1 record. Check if the remaining record in this node is // actually the one we want to remove if (isSame(node.records[0], record)) { // The remaining record is the one we want to remove. Remove the whole node from the tree if (this.root.key === node.key) { // We're removing the root element. Create a dummy node that will temporarily take // root's parent role var rootParent = new Node(this, { low: record.low, high: record.low }); rootParent.left = this.root; this.root.parent = rootParent; var removedNode = this.root.remove(node); this.root = rootParent.left; if (this.root !== undefined) { this.root.parent = undefined; } if (removedNode) { removedNode = undefined; this.count--; return true; } else { return false; } } else { var removedNode = this.root.remove(node); if (removedNode) { removedNode = undefined; this.count--; return true; } else { return false; } } } else { // The remaining record is not the one we want to remove return false; } } else { // No records at all in this node?! Shouldn't happen return false; } } }; IntervalTree.prototype.inOrder = function () { return new InOrder(this.root); }; IntervalTree.prototype.preOrder = function () { return new PreOrder(this.root); }; return IntervalTree; }()); var IntervalTreeWithId = /** @class */ (function () { function IntervalTreeWithId() { this.count = 0; } IntervalTreeWithId.prototype.insert = function (record) { if (record.low > record.high) { throw new Error('`low` value must be lower or equal to `high` value'); } if (this.root === undefined) { // Base case: Tree is empty, new node becomes root this.root = new NodeWithId(this, record); this.count++; return true; } else { // Otherwise, check if node already exists with the same key var node = this.root.searchExisting(record.low); if (node !== undefined) { // Check the records in this node if there already is the one with same low, high, data if(node.records[record.id]) { return false; } // Add the record to the node node.records[record.id] = record; node.recordsCount++; // Update max of the node and its parents if necessary if (record.high > node.max) { node.max = record.high; if (node.parent) { node.parent.updateMaxOfParents(); } } this.count++; return true; } else { // Node with this key doesn't already exist. Call insert function on root's node this.root.insert(record); this.count++; return true; } } }; IntervalTreeWithId.prototype.search = function (low, high, opt_output) { if (!opt_output) { opt_output = []; } if (this.root !== undefined) { this.root.search(low, high, opt_output); } return opt_output; }; IntervalTreeWithId.prototype.searchExisting = function (low, high) { if (this.root) { var node = this.root.searchExisting(low); if (node) { for (var i in node.records) { if (node.records.hasOwnProperty(i)) { if (node.records[i].high === high) { return node.records[i]; } } } } } }; IntervalTreeWithId.prototype.searchAny = function (low, high) { if (this.root) { return this.root.searchAny(low, high); } }; IntervalTreeWithId.prototype.remove = function (record) { if (this.root === undefined) { // Tree is empty; nothing to remove return false; } else { var node = this.root.searchExisting(record.low); if (node === undefined) { return false; } else if (node.recordsCount > 1) { var removedRecord = void 0; // Node with this key has 2 or more records. Find the one we need and remove it if(node.records[record.id]) { removedRecord = node.records[record.v]; delete node.records[record.id]; node.recordsCount--; } if (removedRecord) { removedRecord = undefined; // Update max of that node and its parents if necessary if (record.high === node.max) { var nodeHigh = node.getNodeHigh(); if (node.left !== undefined && node.right !== undefined) { node.max = Math.max(Math.max(node.left.max, node.right.max), nodeHigh); } else if (node.left !== undefined && node.right === undefined) { node.max = Math.max(node.left.max, nodeHigh); } else if (node.left === undefined && node.right !== undefined) { node.max = Math.max(node.right.max, nodeHigh); } else { node.max = nodeHigh; } if (node.parent) { node.parent.updateMaxOfParents(); } } this.count--; return true; } else { return false; } } else if (node.recordsCount === 1) { // Node with this key has only 1 record. Check if the remaining record in this node is // actually the one we want to remove if (node.records[record.id]) { // The remaining record is the one we want to remove. Remove the whole node from the tree if (this.root.key === node.key) { // We're removing the root element. Create a dummy node that will temporarily take // root's parent role var rootParent = new NodeWithId(this, { low: record.low, high: record.low }); rootParent.left = this.root; this.root.parent = rootParent; var removedNode = this.root.remove(node); this.root = rootParent.left; if (this.root !== undefined) { this.root.parent = undefined; } if (removedNode) { removedNode = undefined; this.count--; return true; } else { return false; } } else { var removedNode = this.root.remove(node); if (removedNode) { removedNode = undefined; this.count--; return true; } else { return false; } } } else { // The remaining record is not the one we want to remove return false; } } else { // No records at all in this node?! Shouldn't happen return false; } } }; IntervalTreeWithId.prototype.inOrder = function () { return new InOrder(this.root); }; IntervalTreeWithId.prototype.preOrder = function () { return new PreOrder(this.root); }; return IntervalTreeWithId; }()); var DataIntervalTree = /** @class */ (function () { function DataIntervalTree() { this.tree = new IntervalTree(); } DataIntervalTree.prototype.insert = function (low, high, data) { return this.tree.insert({ low: low, high: high, data: data }); }; DataIntervalTree.prototype.remove = function (low, high, data) { return this.tree.remove({ low: low, high: high, data: data }); }; DataIntervalTree.prototype.searchNodes = function (low, high) { return this.tree.search(low, high); }; DataIntervalTree.prototype.search = function (low, high) { return this.searchNodes(low, high).map(function (v) { return v.data; }); }; DataIntervalTree.prototype.searchExisting = function (low, high) { var record = this.tree.searchExisting(low, high); return record ? record.data : undefined; }; DataIntervalTree.prototype.searchAny = function (low, high) { var record = this.tree.searchAny(low, high); return record ? record.data : undefined; }; DataIntervalTree.prototype.inOrder = function () { return this.tree.inOrder(); }; DataIntervalTree.prototype.preOrder = function () { return this.tree.preOrder(); }; Object.defineProperty(DataIntervalTree.prototype, "count", { get: function () { return this.tree.count; }, enumerable: true, configurable: true }); return DataIntervalTree; }()); var DataIntervalTreeWithId = /** @class */ (function () { function DataIntervalTree() { this.tree = new IntervalTreeWithId(); } DataIntervalTree.prototype.insert = function (low, high, data, id) { return this.tree.insert({ low: low, high: high, data: data, id: id }); }; DataIntervalTree.prototype.remove = function (low, high, data, id) { return this.tree.remove({ low: low, high: high, data: data, id: id }); }; DataIntervalTree.prototype.searchNodes = function (low, high) { return this.tree.search(low, high); }; DataIntervalTree.prototype.search = function (low, high) { return this.searchNodes(low, high).map(function (v) { return v.data; }); }; DataIntervalTree.prototype.searchExisting = function (low, high) { var record = this.tree.searchExisting(low, high); return record ? record.data : undefined; }; DataIntervalTree.prototype.searchAny = function (low, high) { var record = this.tree.searchAny(low, high); return record ? record.data : undefined; }; DataIntervalTree.prototype.inOrder = function () { return this.tree.inOrder(); }; DataIntervalTree.prototype.preOrder = function () { return this.tree.preOrder(); }; Object.defineProperty(DataIntervalTree.prototype, "count", { get: function () { return this.tree.count; }, enumerable: true, configurable: true }); return DataIntervalTree; }()); var DataIntervalTree2D = /** @class */ (function () { function DataIntervalTree2D() { this.tree = new IntervalTree(); } DataIntervalTree2D.prototype.insert = function(bbox, data) { var record = this.tree.searchExisting(bbox.r1, bbox.r2); if (!record) { record = {low: bbox.r1, high: bbox.r2, data: new IntervalTree()}; this.tree.insert(record); } record.data.insert({low: bbox.c1, high: bbox.c2, data: data}); }; DataIntervalTree2D.prototype.remove = function(bbox, data) { var record = this.tree.searchExisting(bbox.r1, bbox.r2); if (record) { record.data.remove({low: bbox.c1, high: bbox.c2, data: data}); } }; DataIntervalTree2D.prototype.searchNodes = function(bbox) { var res = []; var records = this.tree.search(bbox.r1, bbox.r2); for (var i = 0; i < records.length; i++) { records[i].data.search(bbox.c1, bbox.c2, res); } return res; }; DataIntervalTree2D.prototype.searchAny = function(bbox) { var any; var records = this.tree.search(bbox.r1, bbox.r2); for (var i = 0; i < records.length; i++) { any = records[i].data.searchAny(bbox.c1, bbox.c2); if (any) { return any.data; } } return null; }; return DataIntervalTree2D; }()); var InOrder = /** @class */ (function () { function InOrder(startNode) { this.stack = []; if (startNode !== undefined) { this.push(startNode); } } InOrder.prototype.next = function () { // Will only happen if stack is empty and pop is called if (this.currentNode === undefined) { return { done: true, value: undefined, }; } // Process this node if (this.i < this.currentNode.records.length) { return { done: false, value: this.currentNode.records[this.i++], }; } if (this.currentNode.right !== undefined) { this.push(this.currentNode.right); } else { // Might pop the last and set this.currentNode = undefined this.pop(); } return this.next(); }; InOrder.prototype.push = function (node) { this.currentNode = node; this.i = 0; while (this.currentNode.left !== undefined) { this.stack.push(this.currentNode); this.currentNode = this.currentNode.left; } }; InOrder.prototype.pop = function () { this.currentNode = this.stack.pop(); this.i = 0; }; return InOrder; }()); if (typeof Symbol === 'function') { InOrder.prototype[Symbol.iterator] = function () { return this; }; } var PreOrder = /** @class */ (function () { function PreOrder(startNode) { this.stack = []; this.i = 0; this.currentNode = startNode; } PreOrder.prototype.next = function () { // Will only happen if stack is empty and pop is called, // which only happens if there is no right node (i.e we are done) if (this.currentNode === undefined) { return { done: true, value: undefined, }; } // Process this node if (this.i < this.currentNode.records.length) { return { done: false, value: this.currentNode.records[this.i++], }; } if (this.currentNode.right !== undefined) { this.push(this.currentNode.right); } if (this.currentNode.left !== undefined) { this.push(this.currentNode.left); } this.pop(); return this.next(); }; PreOrder.prototype.push = function (node) { this.stack.push(node); }; PreOrder.prototype.pop = function () { this.currentNode = this.stack.pop(); this.i = 0; }; return PreOrder; }()); if (typeof Symbol === 'function') { PreOrder.prototype[Symbol.iterator] = function () { return this; }; } //----------------------------------------------------------export---------------------------------------------------- window['AscCommon'] = window['AscCommon'] || {}; window['AscCommon'].DataIntervalTree = DataIntervalTree; window['AscCommon'].DataIntervalTreeWithId = DataIntervalTreeWithId; window['AscCommon'].DataIntervalTree2D = DataIntervalTree2D; }(window));