1523 lines
62 KiB
JavaScript
1523 lines
62 KiB
JavaScript
/*
|
|
* (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));
|