mirror of
https://github.com/penpot/penpot.git
synced 2025-08-07 14:38:33 +02:00
Improved interval tree impl and add the ability to remove by id.
This commit is contained in:
parent
a01cbfa350
commit
461bdca375
1 changed files with 129 additions and 61 deletions
190
vendor/intervaltree/core.js
vendored
190
vendor/intervaltree/core.js
vendored
|
@ -4,11 +4,9 @@
|
||||||
* @author Andrey Antukh <niwi@niwi.nz>, 2016
|
* @author Andrey Antukh <niwi@niwi.nz>, 2016
|
||||||
* @license MIT License <https://opensource.org/licenses/MIT>
|
* @license MIT License <https://opensource.org/licenses/MIT>
|
||||||
*/
|
*/
|
||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
goog.provide("intervaltree.core");
|
goog.provide("intervaltree.core");
|
||||||
goog.provide("intervaltree.core.IntervalTree");
|
|
||||||
|
|
||||||
goog.require("goog.asserts");
|
goog.require("goog.asserts");
|
||||||
goog.require("goog.array");
|
goog.require("goog.array");
|
||||||
|
|
||||||
|
@ -16,6 +14,8 @@ goog.scope(function() {
|
||||||
const assert = goog.asserts.assert;
|
const assert = goog.asserts.assert;
|
||||||
const every = goog.array.every;
|
const every = goog.array.every;
|
||||||
|
|
||||||
|
const ID_SYM = Symbol.for("intervaltree.core:id-sym");
|
||||||
|
|
||||||
// --- Types Declaration
|
// --- Types Declaration
|
||||||
|
|
||||||
class Interval {
|
class Interval {
|
||||||
|
@ -43,38 +43,20 @@ goog.scope(function() {
|
||||||
class IntervalTree {
|
class IntervalTree {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.root = null;
|
this.root = null;
|
||||||
}
|
this.byId = new Map();
|
||||||
|
|
||||||
add(item) {
|
|
||||||
// Coerce to interval
|
|
||||||
const interval = makeInterval(item);
|
|
||||||
const node = new Node(interval);
|
|
||||||
|
|
||||||
this.root = add(this.root, node);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
remove(item) {
|
|
||||||
const interval = makeInterval(item)
|
|
||||||
this.root = remove(this.root, interval);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
contains(point) {
|
|
||||||
assert(goog.isNumber(point));
|
|
||||||
assert(this.root !== null);
|
|
||||||
return contains(this.root, point);
|
|
||||||
}
|
|
||||||
|
|
||||||
search(item) {
|
|
||||||
const interval = makeInterval(item);
|
|
||||||
return search(this.root, interval);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Private Api (Implementation)
|
// --- Private Api (Implementation)
|
||||||
|
|
||||||
function add(root, node) {
|
const nextId = (function() {
|
||||||
|
let counter = 0;
|
||||||
|
return function() {
|
||||||
|
return counter++;
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
|
function addNode(root, node) {
|
||||||
if (root === null) {
|
if (root === null) {
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
@ -85,9 +67,9 @@ goog.scope(function() {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (node.interval.start <= root.interval.start) {
|
if (node.interval.start <= root.interval.start) {
|
||||||
root.left = add(root.left, node);
|
root.left = addNode(root.left, node);
|
||||||
} else {
|
} else {
|
||||||
root.right = add(root.right, node);
|
root.right = addNode(root.right, node);
|
||||||
}
|
}
|
||||||
|
|
||||||
root.maxEnd = calculateMaxEnd(root);
|
root.maxEnd = calculateMaxEnd(root);
|
||||||
|
@ -115,11 +97,16 @@ goog.scope(function() {
|
||||||
return root;
|
return root;
|
||||||
}
|
}
|
||||||
|
|
||||||
function remove(root, interval) {
|
function removeInterval(root, index, interval) {
|
||||||
if (root === null) {
|
if (root === null) {
|
||||||
return root;
|
return root;
|
||||||
} else if (root.interval.start === interval.start &&
|
} else if (root.interval.start === interval.start &&
|
||||||
root.interval.end === interval.end) {
|
root.interval.end === interval.end) {
|
||||||
|
|
||||||
|
// Remove interval from the index.
|
||||||
|
const intervalId = root.interval[ID_SYM];
|
||||||
|
index.delete(intervalId);
|
||||||
|
|
||||||
if (root.left === null) {
|
if (root.left === null) {
|
||||||
return root.right;
|
return root.right;
|
||||||
} else if (root.right === null) {
|
} else if (root.right === null) {
|
||||||
|
@ -134,8 +121,8 @@ goog.scope(function() {
|
||||||
return newroot;
|
return newroot;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
root.left = remove(root.left, interval);
|
root.left = remove(root.left, index, interval);
|
||||||
root.right = remove(root.right, interval);
|
root.right = remove(root.right, index, interval);
|
||||||
|
|
||||||
root.height = calculateHeight(root);
|
root.height = calculateHeight(root);
|
||||||
root.maxEnd = calculateMaxEnd(root);
|
root.maxEnd = calculateMaxEnd(root);
|
||||||
|
@ -223,7 +210,7 @@ goog.scope(function() {
|
||||||
return y;
|
return y;
|
||||||
}
|
}
|
||||||
|
|
||||||
function contains(root, point) {
|
function containsPoint(root, point) {
|
||||||
if (root.interval.start <= point &&
|
if (root.interval.start <= point &&
|
||||||
root.interval.end >= point) {
|
root.interval.end >= point) {
|
||||||
return true;
|
return true;
|
||||||
|
@ -231,11 +218,11 @@ goog.scope(function() {
|
||||||
let result = false;
|
let result = false;
|
||||||
|
|
||||||
if (root.left && root.left.maxEnd >= point) {
|
if (root.left && root.left.maxEnd >= point) {
|
||||||
result = result || contains(root.left, point);
|
result = result || containsPoint(root.left, point);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (root.right && root.right.maxEnd >= point) {
|
if (root.right && root.right.maxEnd >= point) {
|
||||||
result = result || contains(root.right, point);
|
result = result || containsPoint(root.right, point);
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
@ -249,8 +236,7 @@ goog.scope(function() {
|
||||||
(b.start <= a.end && b.end >= a.end));
|
(b.start <= a.end && b.end >= a.end));
|
||||||
}
|
}
|
||||||
|
|
||||||
function search(root, interval) {
|
function searchSingleInterval(root, interval) {
|
||||||
console.log("1111");
|
|
||||||
if (isIntervalIntersect(root.interval, interval)) {
|
if (isIntervalIntersect(root.interval, interval)) {
|
||||||
return root.interval;
|
return root.interval;
|
||||||
} else {
|
} else {
|
||||||
|
@ -268,9 +254,29 @@ goog.scope(function() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Public Api
|
function searchInterval(root, interval) {
|
||||||
|
const result = new Set();
|
||||||
|
|
||||||
function makeInterval(value) {
|
if (isIntervalIntersect(root.interval, interval)) {
|
||||||
|
result.add(root.interval);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (root.left && root.left.maxEnd >= interval.start) {
|
||||||
|
for (let item of searchMany(root.left, interval)) {
|
||||||
|
result.add(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (root.right && root.right.maxEnd >= interval.start) {
|
||||||
|
for (let item of searchMany(root.right, interval)) {
|
||||||
|
result.add(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createInterval(value) {
|
||||||
if (value instanceof Interval) {
|
if (value instanceof Interval) {
|
||||||
return value
|
return value
|
||||||
} else if (goog.isArray(value)) {
|
} else if (goog.isArray(value)) {
|
||||||
|
@ -301,10 +307,12 @@ goog.scope(function() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
function makeTree(items) {
|
// --- Public Api
|
||||||
|
|
||||||
|
function create(items) {
|
||||||
const tree = new IntervalTree();
|
const tree = new IntervalTree();
|
||||||
|
|
||||||
if (goog.isArrayLike(items)) {
|
if (goog.isArray(items)) {
|
||||||
for(let item of items) {
|
for(let item of items) {
|
||||||
tree.add(item);
|
tree.add(item);
|
||||||
}
|
}
|
||||||
|
@ -313,18 +321,77 @@ goog.scope(function() {
|
||||||
return tree;
|
return tree;
|
||||||
}
|
}
|
||||||
|
|
||||||
const module = intervaltree.core;
|
function add(tree, id, item) {
|
||||||
|
assert(tree instanceof IntervalTree);
|
||||||
|
|
||||||
// Types
|
if (id && !item) {
|
||||||
module.IntervalTree = IntervalTree;
|
item = id;
|
||||||
module.Interval = Interval;
|
id = nextId();
|
||||||
module.Node = Node;
|
}
|
||||||
|
|
||||||
// Constructors
|
// Coerce to interval
|
||||||
module.interval = makeInterval;
|
const interval = createInterval(item);
|
||||||
module.create = makeTree;
|
const node = new Node(interval);
|
||||||
|
|
||||||
|
interval[ID_SYM] = id;
|
||||||
|
|
||||||
|
tree.byId.set(id, interval);
|
||||||
|
tree.root = addNode(tree.root, node);
|
||||||
|
return tree;
|
||||||
|
}
|
||||||
|
|
||||||
|
function clear(tree) {
|
||||||
|
assert(tree instanceof IntervalTree);
|
||||||
|
this.root = null;
|
||||||
|
this.byId.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
function remove(tree, item) {
|
||||||
|
assert(tree instanceof IntervalTree);
|
||||||
|
|
||||||
|
const interval = createInterval(item);
|
||||||
|
tree.root = removeInterval(tree.root, tree.byId, interval);
|
||||||
|
return tree;
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeById(tree, id) {
|
||||||
|
assert(tree instanceof IntervalTree);
|
||||||
|
|
||||||
|
if (tree.byId.has(id)) {
|
||||||
|
const interval = this.byId.get(id);
|
||||||
|
remove(tree, interval);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function contains(tree, point) {
|
||||||
|
assert(tree instanceof IntervalTree);
|
||||||
|
assert(goog.isNumber(point));
|
||||||
|
|
||||||
|
return containsPoint(tree.root, point);
|
||||||
|
}
|
||||||
|
|
||||||
|
function search(tree, item) {
|
||||||
|
assert(tree instanceof IntervalTree);
|
||||||
|
const interval = createInterval(item);
|
||||||
|
return Array.from(searchInterval(tree.root, interval));
|
||||||
|
}
|
||||||
|
|
||||||
|
function searchSingle(tree, item) {
|
||||||
|
assert(tree instanceof IntervalTree);
|
||||||
|
const interval = createInterval(item);
|
||||||
|
return searchSingleInterval(tree.root, interval);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Api
|
||||||
|
intervaltree.core.create = create;
|
||||||
|
intervaltree.core.add = add;
|
||||||
|
intervaltree.core.remove = remove;
|
||||||
|
intervaltree.core.removeById = removeById;
|
||||||
|
intervaltree.core.contains = contains;
|
||||||
|
intervaltree.core.search = search;
|
||||||
|
intervaltree.core.searchSingle = searchSingle;
|
||||||
|
|
||||||
|
// Test
|
||||||
module.test = function() {
|
module.test = function() {
|
||||||
// const util = require('util');
|
// const util = require('util');
|
||||||
|
|
||||||
|
@ -334,21 +401,22 @@ goog.scope(function() {
|
||||||
[10,14], [-10, 1], [9, 22],
|
[10,14], [-10, 1], [9, 22],
|
||||||
]);
|
]);
|
||||||
console.timeEnd("init");
|
console.timeEnd("init");
|
||||||
|
|
||||||
console.dir(tree, { depth: 5});
|
console.dir(tree, { depth: 5});
|
||||||
|
|
||||||
const n = 6;
|
const n = 2;
|
||||||
console.time("search")
|
console.time("search");
|
||||||
console.log("result to", n, "=>", tree.search(n));
|
console.log("SEARCH***********************************");
|
||||||
console.timeEnd("search")
|
console.log(`RESULT searchMany(${n}):`);
|
||||||
|
console.log(tree.searchMany(n));
|
||||||
|
console.timeEnd("search");
|
||||||
|
|
||||||
console.time("remove");
|
console.time("remove");
|
||||||
// tree.remove([4,9]);
|
// // tree.remove([4,9]);
|
||||||
tree.remove([9, 22]);
|
// tree.remove([9, 22]);
|
||||||
tree.remove([-10, 1]);
|
tree.remove([-10, 1]);
|
||||||
|
|
||||||
console.dir(tree, { depth: 5});
|
|
||||||
console.timeEnd("remove");
|
console.timeEnd("remove");
|
||||||
|
|
||||||
|
// console.dir(tree, { depth: 5});
|
||||||
};
|
};
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue