/** * intervaltree * * @author Andrey Antukh , 2016 * @license MIT License */ "use strict"; goog.provide("intervaltree.core"); goog.provide("intervaltree.core.IntervalTree"); goog.require("goog.asserts"); goog.require("goog.array"); goog.scope(function() { const assert = goog.asserts.assert; const every = goog.array.every; // --- Types Declaration class Interval { constructor(start, end, data) { this.start = start; this.end = end; this.data = data || null; } toString() { return `v=${this.start},${this.end} d=${this.data}`; } } class Node { constructor(interval) { this.interval = interval; this.left = null; this.right = null; this.maxEnd = interval.end; this.height = 1; } } class IntervalTree { constructor() { this.root = null; } 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) function add(root, node) { if (root === null) { return node; } if (root.interval.start === node.interval.start && root.interval.end == node.interval.end) { return root; } if (node.interval.start <= root.interval.start) { root.left = add(root.left, node); } else { root.right = add(root.right, node); } root.maxEnd = calculateMaxEnd(root); root.height = calculateHeight(root); const balance = calculateBalance(root); if (balance > 1) { if (node.interval.start < root.left.interval.start) { return rotateRight(root); } // else { // root.left = rotateLeft(root.left); // return rotateRight(root); // } } else if (balance < -1) { if (node.interval.start > root.right.interval.start) { return rotateLeft(root); } // else { // root.right = rotateRight(root.right); // return rotateLeft(current); // } } return root; } function remove(root, interval) { if (root === null) { return root; } else if (root.interval.start === interval.start && root.interval.end === interval.end) { if (root.left === null) { return root.right; } else if (root.right === null) { return root.left; } else { const result = removeLeftMost(root.right); const newroot = result[0]; const newright = result[1]; newroot.left = root.left; newroot.right = newright; return newroot; } } else { root.left = remove(root.left, interval); root.right = remove(root.right, interval); root.height = calculateHeight(root); root.maxEnd = calculateMaxEnd(root); return root; } } function removeLeftMost(root) { if (root === null) { return [null, null]; } else if (root.left === null) { return [root, root.right]; } else { const result = removeLeftMost(root.left); const newroot = result[0]; const newleft = result[1]; root.left = newleft; return [newroot, root]; } } function calculateMaxEnd(node) { const left = node.left ? node.left.maxEnd : 0; const right = node.right ? node.right.maxEnd: 0; return Math.max(node.interval.end, Math.max(left, right)); } function calculateHeight(node) { const left = node.left ? node.left.height : 0; const right = node.right ? node.right.height: 0; return Math.max(left, right) + 1; } function calculateBalance(node) { if (node === null) { return 0; } const left = node.left ? node.left.height: 0; const right = node.right ? node.right.height: 0; return left - right; } function rotateLeft(z) { const y = z.right; const x = y.right; const t1 = z.left; const t2 = y.left; z.left = t1; z.right = t2; y.left = z; y.right = x; z.height = calculateHeight(z); z.maxEnd = calculateMaxEnd(z); y.height = calculateHeight(z); y.maxEnd = calculateMaxEnd(z); return y; } function rotateRight(z) { const y = z.left; const x = y.left; const t3 = y.right; const t4 = z.right; z.left = t3; z.right = t4; y.left = x; y.right = z; z.height = calculateHeight(z); z.maxEnd = calculateMaxEnd(z); y.height = calculateHeight(z); y.maxEnd = calculateMaxEnd(z); return y; } function contains(root, point) { if (root.interval.start <= point && root.interval.end >= point) { return true; } else { let result = false; if (root.left && root.left.maxEnd >= point) { result = result || contains(root.left, point); } if (root.right && root.right.maxEnd >= point) { result = result || contains(root.right, point); } return result; } } function isIntervalIntersect(a, b) { return ((a.start <= b.start && a.end >= b.start) || (a.start <= b.end && a.end >= b.end) || (b.start <= a.start && b.end >= a.start) || (b.start <= a.end && b.end >= a.end)); } function search(root, interval) { console.log("1111"); if (isIntervalIntersect(root.interval, interval)) { return root.interval; } else { let result = null; if (root.left && root.left.maxEnd >= interval.start) { result = result || search(root.left, interval); } if (root.right && root.right.maxEnd >= interval.start) { result = result || search(root.right, interval); } return result; } } // --- Public Api function makeInterval(value) { if (value instanceof Interval) { return value } else if (goog.isArray(value)) { if (value.length === 1 && goog.isNumber(value[0])) { return new Interval(value[0], value[0]); } else if (value.length === 2 && goog.isNumber(value[0])) { if (goog.isNumber(value[1])) { return new Interval(value[0], value[1]); } else { return new Interval(value[0], value[0], value[1]); } } else if (value.length === 3 && goog.isNumber(value[0]) && goog.isNumber(value[1])) { return new Interval(value[0], value[1], value[2]); } } else if (arguments.length === 1 && goog.isNumber(value)) { return new Interval(value, value); } else if (arguments.length >= 2 && goog.isNumber(value)) { if (goog.isNumber(arguments[1])) { return new Interval(value, arguments[1], arguments[2]); } else { return new Interval(value, value, arguments[1]); } } else { throw new Error("Unexpected input."); } }; function makeTree(items) { const tree = new IntervalTree(); if (goog.isArrayLike(items)) { for(let item of items) { tree.add(item); } } return tree; } const module = intervaltree.core; // Types module.IntervalTree = IntervalTree; module.Interval = Interval; module.Node = Node; // Constructors module.interval = makeInterval; module.create = makeTree; module.test = function() { // const util = require('util'); console.time("init"); const tree = module.create([ [1,5], [-5, 10], [4, 9], [10,14], [-10, 1], [9, 22], ]); console.timeEnd("init"); console.dir(tree, { depth: 5}); const n = 6; console.time("search") console.log("result to", n, "=>", tree.search(n)); console.timeEnd("search") console.time("remove"); // tree.remove([4,9]); tree.remove([9, 22]); tree.remove([-10, 1]); console.dir(tree, { depth: 5}); console.timeEnd("remove"); }; });