All Downloads are FREE. Search and download functionalities are using the official Maven repository.

package.dist.main.esm.js Maven / Gradle / Ivy

The newest version!
/**
 * Created by Alex Bol on 4/1/2017.
 */

/**
 * Interval is a pair of numbers or a pair of any comparable objects on which may be defined predicates
 * *equal*, *less* and method *max(p1, p1)* that returns maximum in a pair.
 * When interval is an object rather than pair of numbers, this object should have properties *low*, *high*, *max*
 * and implement methods *less_than(), equal_to(), intersect(), not_intersect(), clone(), output()*.
 * Two static methods *comparable_max(), comparable_less_than()* define how to compare values in pair. 
* This interface is described in typescript definition file *index.d.ts* * * Axis aligned rectangle is an example of such interval. * We may look at rectangle as an interval between its low left and top right corners. * See **Box** class in [flatten-js](https://github.com/alexbol99/flatten-js) library as the example * of Interval interface implementation * @type {Interval} */ const Interval = class Interval { /** * Accept two comparable values and creates new instance of interval * Predicate Interval.comparable_less(low, high) supposed to return true on these values * @param low * @param high */ constructor(low, high) { this.low = low; this.high = high; } /** * Clone interval * @returns {Interval} */ clone() { return new Interval(this.low, this.high); } /** * Propery max returns clone of this interval * @returns {Interval} */ get max() { return this.clone(); // this.high; } /** * Predicate returns true is this interval less than other interval * @param other_interval * @returns {boolean} */ less_than(other_interval) { return this.low < other_interval.low || this.low == other_interval.low && this.high < other_interval.high; } /** * Predicate returns true is this interval equals to other interval * @param other_interval * @returns {boolean} */ equal_to(other_interval) { return this.low == other_interval.low && this.high == other_interval.high; } /** * Predicate returns true if this interval intersects other interval * @param other_interval * @returns {boolean} */ intersect(other_interval) { return !this.not_intersect(other_interval); } /** * Predicate returns true if this interval does not intersect other interval * @param other_interval * @returns {boolean} */ not_intersect(other_interval) { return (this.high < other_interval.low || other_interval.high < this.low); } /** * Returns new interval merged with other interval * @param {Interval} interval - Other interval to merge with * @returns {Interval} */ merge(other_interval) { return new Interval( this.low === undefined ? other_interval.low : Math.min(this.low, other_interval.low), this.high === undefined ? other_interval.high : Math.max(this.high, other_interval.high) ); } /** * Returns how key should return */ output() { return [this.low, this.high]; } /** * Function returns maximum between two comparable values * @param interval1 * @param interval2 * @returns {Interval} */ static comparable_max(interval1, interval2) { return interval1.merge(interval2); } /** * Predicate returns true if first value less than second value * @param val1 * @param val2 * @returns {boolean} */ static comparable_less_than(val1, val2 ) { return val1 < val2; } }; /** * Created by Alex Bol on 3/28/2017. */ // module.exports = { // RB_TREE_COLOR_RED: 0, // RB_TREE_COLOR_BLACK: 1 // }; const RB_TREE_COLOR_RED = 0; const RB_TREE_COLOR_BLACK = 1; /** * Created by Alex Bol on 4/1/2017. */ class Node { constructor(key = undefined, value = undefined, left = null, right = null, parent = null, color = RB_TREE_COLOR_BLACK) { this.left = left; // reference to left child node this.right = right; // reference to right child node this.parent = parent; // reference to parent node this.color = color; this.item = {key: key, value: value}; // key is supposed to be instance of Interval /* If not, this should by an array of two numbers */ if (key && key instanceof Array && key.length == 2) { if (!Number.isNaN(key[0]) && !Number.isNaN(key[1])) { this.item.key = new Interval(Math.min(key[0], key[1]), Math.max(key[0], key[1])); } } this.max = this.item.key ? this.item.key.max : undefined; } isNil() { return (this.item.key === undefined && this.item.value === undefined && this.left === null && this.right === null && this.color === RB_TREE_COLOR_BLACK); } _value_less_than(other_node) { return this.item.value && other_node.item.value && this.item.value.less_than ? this.item.value.less_than(other_node.item.value) : this.item.value < other_node.item.value; } less_than(other_node) { // if tree stores only keys if (this.item.value === this.item.key && other_node.item.value === other_node.item.key) { return this.item.key.less_than(other_node.item.key); } else { // if tree stores keys and values return this.item.key.less_than(other_node.item.key) || this.item.key.equal_to((other_node.item.key)) && this._value_less_than(other_node) } } _value_equal(other_node) { return this.item.value && other_node.item.value && this.item.value.equal_to ? this.item.value.equal_to(other_node.item.value) : this.item.value == other_node.item.value; } equal_to(other_node) { // if tree stores only keys if (this.item.value === this.item.key && other_node.item.value === other_node.item.key) { return this.item.key.equal_to(other_node.item.key); } else { // if tree stores keys and values return this.item.key.equal_to(other_node.item.key) && this._value_equal(other_node); } } intersect(other_node) { return this.item.key.intersect(other_node.item.key); } copy_data(other_node) { this.item.key = other_node.item.key; this.item.value = other_node.item.value; } update_max() { // use key (Interval) max property instead of key.high this.max = this.item.key ? this.item.key.max : undefined; if (this.right && this.right.max) { const comparable_max = this.item.key.constructor.comparable_max; // static method this.max = comparable_max(this.max, this.right.max); } if (this.left && this.left.max) { const comparable_max = this.item.key.constructor.comparable_max; // static method this.max = comparable_max(this.max, this.left.max); } } // Other_node does not intersect any node of left subtree, if this.left.max < other_node.item.key.low not_intersect_left_subtree(search_node) { const comparable_less_than = this.item.key.constructor.comparable_less_than; // static method let high = this.left.max.high !== undefined ? this.left.max.high : this.left.max; return comparable_less_than(high, search_node.item.key.low); } // Other_node does not intersect right subtree if other_node.item.key.high < this.right.key.low not_intersect_right_subtree(search_node) { const comparable_less_than = this.item.key.constructor.comparable_less_than; // static method let low = this.right.max.low !== undefined ? this.right.max.low : this.right.item.key.low; return comparable_less_than(search_node.item.key.high, low); } } /** * Created by Alex Bol on 3/31/2017. */ // const nil_node = new Node(); /** * Implementation of interval binary search tree
* Interval tree stores items which are couples of {key:interval, value: value}
* Interval is an object with high and low properties or simply pair [low,high] of numeric values
* @type {IntervalTree} */ class IntervalTree { /** * Construct new empty instance of IntervalTree */ constructor() { this.root = null; this.nil_node = new Node(); } /** * Returns number of items stored in the interval tree * @returns {number} */ get size() { let count = 0; this.tree_walk(this.root, () => count++); return count; } /** * Returns array of sorted keys in the ascending order * @returns {Array} */ get keys() { let res = []; this.tree_walk(this.root, (node) => res.push( node.item.key.output ? node.item.key.output() : node.item.key )); return res; } /** * Return array of values in the ascending keys order * @returns {Array} */ get values() { let res = []; this.tree_walk(this.root, (node) => res.push(node.item.value)); return res; } /** * Returns array of items ( pairs) in the ascended keys order * @returns {Array} */ get items() { let res = []; this.tree_walk(this.root, (node) => res.push({ key: node.item.key.output ? node.item.key.output() : node.item.key, value: node.item.value })); return res; } /** * Returns true if tree is empty * @returns {boolean} */ isEmpty() { return (this.root == null || this.root == this.nil_node); } /** * Clear tree */ clear() { this.root = null; } /** * Insert new item into interval tree * @param {Interval} key - interval object or array of two numbers [low, high] * @param {any} value - value representing any object (optional) * @returns {Node} returns reference to inserted node as an object {key:interval, value: value} */ insert(key, value = key) { if (key === undefined) return; let insert_node = new Node(key, value, this.nil_node, this.nil_node, null, RB_TREE_COLOR_RED); this.tree_insert(insert_node); this.recalc_max(insert_node); return insert_node; } /** * Returns true if item {key,value} exist in the tree * @param {Interval} key - interval correspondent to keys stored in the tree * @param {any} value - value object to be checked * @returns {boolean} true if item {key, value} exist in the tree, false otherwise */ exist(key, value = key) { let search_node = new Node(key, value); return this.tree_search(this.root, search_node) ? true : false; } /** * Remove entry {key, value} from the tree * @param {Interval} key - interval correspondent to keys stored in the tree * @param {any} value - value object * @returns {boolean} true if item {key, value} deleted, false if not found */ remove(key, value = key) { let search_node = new Node(key, value); let delete_node = this.tree_search(this.root, search_node); if (delete_node) { this.tree_delete(delete_node); } return delete_node; } /** * Returns array of entry values which keys intersect with given interval
* If no values stored in the tree, returns array of keys which intersect given interval * @param {Interval} interval - search interval, or tuple [low, high] * @param outputMapperFn(value,key) - optional function that maps (value, key) to custom output * @returns {Array} */ search(interval, outputMapperFn = (value, key) => value === key ? key.output() : value) { let search_node = new Node(interval); let resp_nodes = []; this.tree_search_interval(this.root, search_node, resp_nodes); return resp_nodes.map(node => outputMapperFn(node.item.value, node.item.key)) } /** * Returns true if intersection between given and any interval stored in the tree found * @param {Interval} interval - search interval or tuple [low, high] * @returns {boolean} */ intersect_any(interval) { let search_node = new Node(interval); let found = this.tree_find_any_interval(this.root, search_node); return found; } /** * Tree visitor. For each node implement a callback function.
* Method calls a callback function with two parameters (key, value) * @param visitor(key,value) - function to be called for each tree item */ forEach(visitor) { this.tree_walk(this.root, (node) => visitor(node.item.key, node.item.value)); } /** Value Mapper. Walk through every node and map node value to another value * @param callback(value,key) - function to be called for each tree item */ map(callback) { const tree = new IntervalTree(); this.tree_walk(this.root, (node) => tree.insert(node.item.key, callback(node.item.value, node.item.key))); return tree; } recalc_max(node) { let node_current = node; while (node_current.parent != null) { node_current.parent.update_max(); node_current = node_current.parent; } } tree_insert(insert_node) { let current_node = this.root; let parent_node = null; if (this.root == null || this.root == this.nil_node) { this.root = insert_node; } else { while (current_node != this.nil_node) { parent_node = current_node; if (insert_node.less_than(current_node)) { current_node = current_node.left; } else { current_node = current_node.right; } } insert_node.parent = parent_node; if (insert_node.less_than(parent_node)) { parent_node.left = insert_node; } else { parent_node.right = insert_node; } } this.insert_fixup(insert_node); } // After insertion insert_node may have red-colored parent, and this is a single possible violation // Go upwords to the root and re-color until violation will be resolved insert_fixup(insert_node) { let current_node; let uncle_node; current_node = insert_node; while (current_node != this.root && current_node.parent.color == RB_TREE_COLOR_RED) { if (current_node.parent == current_node.parent.parent.left) { // parent is left child of grandfather uncle_node = current_node.parent.parent.right; // right brother of parent if (uncle_node.color == RB_TREE_COLOR_RED) { // Case 1. Uncle is red // re-color father and uncle into black current_node.parent.color = RB_TREE_COLOR_BLACK; uncle_node.color = RB_TREE_COLOR_BLACK; current_node.parent.parent.color = RB_TREE_COLOR_RED; current_node = current_node.parent.parent; } else { // Case 2 & 3. Uncle is black if (current_node == current_node.parent.right) { // Case 2. Current if right child // This case is transformed into Case 3. current_node = current_node.parent; this.rotate_left(current_node); } current_node.parent.color = RB_TREE_COLOR_BLACK; // Case 3. Current is left child. // Re-color father and grandfather, rotate grandfather right current_node.parent.parent.color = RB_TREE_COLOR_RED; this.rotate_right(current_node.parent.parent); } } else { // parent is right child of grandfather uncle_node = current_node.parent.parent.left; // left brother of parent if (uncle_node.color == RB_TREE_COLOR_RED) { // Case 4. Uncle is red // re-color father and uncle into black current_node.parent.color = RB_TREE_COLOR_BLACK; uncle_node.color = RB_TREE_COLOR_BLACK; current_node.parent.parent.color = RB_TREE_COLOR_RED; current_node = current_node.parent.parent; } else { if (current_node == current_node.parent.left) { // Case 5. Current is left child // Transform into case 6 current_node = current_node.parent; this.rotate_right(current_node); } current_node.parent.color = RB_TREE_COLOR_BLACK; // Case 6. Current is right child. // Re-color father and grandfather, rotate grandfather left current_node.parent.parent.color = RB_TREE_COLOR_RED; this.rotate_left(current_node.parent.parent); } } } this.root.color = RB_TREE_COLOR_BLACK; } tree_delete(delete_node) { let cut_node; // node to be cut - either delete_node or successor_node ("y" from 14.4) let fix_node; // node to fix rb tree property ("x" from 14.4) if (delete_node.left == this.nil_node || delete_node.right == this.nil_node) { // delete_node has less then 2 children cut_node = delete_node; } else { // delete_node has 2 children cut_node = this.tree_successor(delete_node); } // fix_node if single child of cut_node if (cut_node.left != this.nil_node) { fix_node = cut_node.left; } else { fix_node = cut_node.right; } // remove cut_node from parent /*if (fix_node != this.nil_node) {*/ fix_node.parent = cut_node.parent; /*}*/ if (cut_node == this.root) { this.root = fix_node; } else { if (cut_node == cut_node.parent.left) { cut_node.parent.left = fix_node; } else { cut_node.parent.right = fix_node; } cut_node.parent.update_max(); // update max property of the parent } this.recalc_max(fix_node); // update max property upward from fix_node to root // COPY DATA !!! // Delete_node becomes cut_node, it means that we cannot hold reference // to node in outer structure and we will have to delete by key, additional search need if (cut_node != delete_node) { delete_node.copy_data(cut_node); delete_node.update_max(); // update max property of the cut node at the new place this.recalc_max(delete_node); // update max property upward from delete_node to root } if (/*fix_node != this.nil_node && */cut_node.color == RB_TREE_COLOR_BLACK) { this.delete_fixup(fix_node); } } delete_fixup(fix_node) { let current_node = fix_node; let brother_node; while (current_node != this.root && current_node.parent != null && current_node.color == RB_TREE_COLOR_BLACK) { if (current_node == current_node.parent.left) { // fix node is left child brother_node = current_node.parent.right; if (brother_node.color == RB_TREE_COLOR_RED) { // Case 1. Brother is red brother_node.color = RB_TREE_COLOR_BLACK; // re-color brother current_node.parent.color = RB_TREE_COLOR_RED; // re-color father this.rotate_left(current_node.parent); brother_node = current_node.parent.right; // update brother } // Derive to cases 2..4: brother is black if (brother_node.left.color == RB_TREE_COLOR_BLACK && brother_node.right.color == RB_TREE_COLOR_BLACK) { // case 2: both nephews black brother_node.color = RB_TREE_COLOR_RED; // re-color brother current_node = current_node.parent; // continue iteration } else { if (brother_node.right.color == RB_TREE_COLOR_BLACK) { // case 3: left nephew red, right nephew black brother_node.color = RB_TREE_COLOR_RED; // re-color brother brother_node.left.color = RB_TREE_COLOR_BLACK; // re-color nephew this.rotate_right(brother_node); brother_node = current_node.parent.right; // update brother // Derive to case 4: left nephew black, right nephew red } // case 4: left nephew black, right nephew red brother_node.color = current_node.parent.color; current_node.parent.color = RB_TREE_COLOR_BLACK; brother_node.right.color = RB_TREE_COLOR_BLACK; this.rotate_left(current_node.parent); current_node = this.root; // exit from loop } } else { // fix node is right child brother_node = current_node.parent.left; if (brother_node.color == RB_TREE_COLOR_RED) { // Case 1. Brother is red brother_node.color = RB_TREE_COLOR_BLACK; // re-color brother current_node.parent.color = RB_TREE_COLOR_RED; // re-color father this.rotate_right(current_node.parent); brother_node = current_node.parent.left; // update brother } // Go to cases 2..4 if (brother_node.left.color == RB_TREE_COLOR_BLACK && brother_node.right.color == RB_TREE_COLOR_BLACK) { // case 2 brother_node.color = RB_TREE_COLOR_RED; // re-color brother current_node = current_node.parent; // continue iteration } else { if (brother_node.left.color == RB_TREE_COLOR_BLACK) { // case 3: right nephew red, left nephew black brother_node.color = RB_TREE_COLOR_RED; // re-color brother brother_node.right.color = RB_TREE_COLOR_BLACK; // re-color nephew this.rotate_left(brother_node); brother_node = current_node.parent.left; // update brother // Derive to case 4: right nephew black, left nephew red } // case 4: right nephew black, left nephew red brother_node.color = current_node.parent.color; current_node.parent.color = RB_TREE_COLOR_BLACK; brother_node.left.color = RB_TREE_COLOR_BLACK; this.rotate_right(current_node.parent); current_node = this.root; // force exit from loop } } } current_node.color = RB_TREE_COLOR_BLACK; } tree_search(node, search_node) { if (node == null || node == this.nil_node) return undefined; if (search_node.equal_to(node)) { return node; } if (search_node.less_than(node)) { return this.tree_search(node.left, search_node); } else { return this.tree_search(node.right, search_node); } } // Original search_interval method; container res support push() insertion // Search all intervals intersecting given one tree_search_interval(node, search_node, res) { if (node != null && node != this.nil_node) { // if (node->left != this.nil_node && node->left->max >= low) { if (node.left != this.nil_node && !node.not_intersect_left_subtree(search_node)) { this.tree_search_interval(node.left, search_node, res); } // if (low <= node->high && node->low <= high) { if (node.intersect(search_node)) { res.push(node); } // if (node->right != this.nil_node && node->low <= high) { if (node.right != this.nil_node && !node.not_intersect_right_subtree(search_node)) { this.tree_search_interval(node.right, search_node, res); } } } tree_find_any_interval(node, search_node) { let found = false; if (node != null && node != this.nil_node) { // if (node->left != this.nil_node && node->left->max >= low) { if (node.left != this.nil_node && !node.not_intersect_left_subtree(search_node)) { found = this.tree_find_any_interval(node.left, search_node); } // if (low <= node->high && node->low <= high) { if (!found) { found = node.intersect(search_node); } // if (node->right != this.nil_node && node->low <= high) { if (!found && node.right != this.nil_node && !node.not_intersect_right_subtree(search_node)) { found = this.tree_find_any_interval(node.right, search_node); } } return found; } local_minimum(node) { let node_min = node; while (node_min.left != null && node_min.left != this.nil_node) { node_min = node_min.left; } return node_min; } // not in use local_maximum(node) { let node_max = node; while (node_max.right != null && node_max.right != this.nil_node) { node_max = node_max.right; } return node_max; } tree_successor(node) { let node_successor; let current_node; let parent_node; if (node.right != this.nil_node) { node_successor = this.local_minimum(node.right); } else { current_node = node; parent_node = node.parent; while (parent_node != null && parent_node.right == current_node) { current_node = parent_node; parent_node = parent_node.parent; } node_successor = parent_node; } return node_successor; } // | right-rotate(T,y) | // y ---------------. x // / \ / \ // x c left-rotate(T,x) a y // / \ <--------------- / \ // a b b c rotate_left(x) { let y = x.right; x.right = y.left; // b goes to x.right if (y.left != this.nil_node) { y.left.parent = x; // x becomes parent of b } y.parent = x.parent; // move parent if (x == this.root) { this.root = y; // y becomes root } else { // y becomes child of x.parent if (x == x.parent.left) { x.parent.left = y; } else { x.parent.right = y; } } y.left = x; // x becomes left child of y x.parent = y; // and y becomes parent of x if (x != null && x != this.nil_node) { x.update_max(); } y = x.parent; if (y != null && y != this.nil_node) { y.update_max(); } } rotate_right(y) { let x = y.left; y.left = x.right; // b goes to y.left if (x.right != this.nil_node) { x.right.parent = y; // y becomes parent of b } x.parent = y.parent; // move parent if (y == this.root) { // x becomes root this.root = x; } else { // y becomes child of x.parent if (y == y.parent.left) { y.parent.left = x; } else { y.parent.right = x; } } x.right = y; // y becomes right child of x y.parent = x; // and x becomes parent of y if (y != null && y != this.nil_node) { y.update_max(); } x = y.parent; if (x != null && x != this.nil_node) { x.update_max(); } } tree_walk(node, action) { if (node != null && node != this.nil_node) { this.tree_walk(node.left, action); // arr.push(node.toArray()); action(node); this.tree_walk(node.right, action); } } /* Return true if all red nodes have exactly two black child nodes */ testRedBlackProperty() { let res = true; this.tree_walk(this.root, function (node) { if (node.color == RB_TREE_COLOR_RED) { if (!(node.left.color == RB_TREE_COLOR_BLACK && node.right.color == RB_TREE_COLOR_BLACK)) { res = false; } } }); return res; } /* Throw error if not every path from root to bottom has same black height */ testBlackHeightProperty(node) { let height = 0; let heightLeft = 0; let heightRight = 0; if (node.color == RB_TREE_COLOR_BLACK) { height++; } if (node.left != this.nil_node) { heightLeft = this.testBlackHeightProperty(node.left); } else { heightLeft = 1; } if (node.right != this.nil_node) { heightRight = this.testBlackHeightProperty(node.right); } else { heightRight = 1; } if (heightLeft != heightRight) { throw new Error('Red-black height property violated'); } height += heightLeft; return height; }; } export { Interval, Node, IntervalTree as default };




© 2015 - 2025 Weber Informatics LLC | Privacy Policy