/* * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * Copyright (c) KALEIDOS INC */ /* * Balanced Binary Search Tree based on the red-black BST * described at "Algorithms" by Robert Sedwick & Kevin Wayne */ "use strict"; goog.provide("app.util.range_tree"); goog.require("cljs.core"); goog.scope(function() { const eq = cljs.core._EQ_; const vec = cljs.core.vec; const nil = cljs.core.nil; const Color = { RED: 1, BLACK: 2 } class Node { constructor(value, data) { this.value = value; this.data = [ data ]; this.left = null; this.right = null; this.color = Color.BLACK; } } // Will store a map from key to list of data // value => [ data ] // The values can be queried in range and the data stored will be retrieved whole // but can be removed/updated individually using clojurescript equality class RangeTree { constructor() { this.root = null; } insert(value, data) { this.root = recInsert(this.root, value, data); this.root.color = Color.BLACK; return this; } remove(value, data) { if (!this.root) { return this; } this.root = recRemoveData(this.root, value, data); const newData = recGet(this.root, value); if (newData && newData.length === 0) { if (!isRed(this.root.left) && !isRed(this.root.right)) { this.root.color = Color.RED; } this.root = recRemoveNode(this.root, value); if (this.root) { this.root.color = Color.BLACK; } } return this; } update (value, oldData, newData) { this.root = recUpdate(this.root, value, oldData, newData); return this; } get(value) { return recGet(this.root, value); } rangeQuery (fromValue, toValue) { return recRangeQuery(this.root, fromValue, toValue, []); } height() { return recHeight(this.root); } isEmpty() { return !this.root; } toString() { const result = []; recToString(this.root, result); return result.join(", "); } asMap() { const result = {}; recTreeAsMap(this.root, result); return result; } } // Tree implementation functions function isRed(branch) { return branch && branch.color === Color.RED; } // Insert recursively in the tree function recInsert (branch, value, data) { if (!branch) { const ret = new Node(value, data); ret.color = Color.RED; return ret; } else if (branch.value === value) { // Find node we'll add to the end of the list branch.data.push(data); } else if (branch.value > value) { // Target value is less than the current value we go left branch.left = recInsert(branch.left, value, data); } else if (branch.value < value) { branch.right = recInsert(branch.right, value, data); } if (isRed(branch.right) && !isRed(branch.left)) { branch = rotateLeft(branch); } if (isRed(branch.left) && isRed(branch.left.left)) { branch = rotateRight(branch); } if (isRed(branch.left) && isRed(branch.right)) { flipColors(branch); } return branch; } // Search for the min node function searchMin(branch) { if (!branch.left) { return branch; } else { return searchMin(branch.left); } } // Remove the leftmost node of the current branch function recRemoveMin(branch) { if (!branch.left) { return null; } if (!isRed(branch.left) && !isRed(branch.left.left)) { branch = moveRedLeft(branch); } branch.left = recRemoveMin(branch.left); return balance(branch); } // Remove the data element for the value given // this will not remove the node, we have to remove the empty node afterwards function recRemoveData(branch, value, data) { if (!branch) { // Not found return branch; } else if (branch.value === value) { // Node found, we remove the data branch.data = branch.data.filter ((it) => !eq(it, data)); return branch; } else if (branch.value > value) { branch.left = recRemoveData (branch.left, value, data); return branch; } else if (branch.value < value) { branch.right = recRemoveData(branch.right, value, data); return branch; } } function recRemoveNode(branch, value) { if (value < branch.value) { if (!isRed(branch.left) && !isRed(branch.left.left)) { branch = moveRedLeft(branch); } branch.left = recRemoveNode(branch.left, value); } else { if (isRed(branch.left)) { branch = rotateRight(branch); } if (value === branch.value && !branch.right) { return null; } if (!isRed(branch.right) && !isRed(branch.right.left)) { branch = moveRedRight(branch); } if (value === branch.value) { const x = searchMin(branch.right); branch.value = x.value; branch.data = x.data; branch.right = recRemoveMin(branch.right); } else { branch.right = recRemoveNode(branch.right, value); } } return balance(branch); } // Retrieve all the data related to value function recGet(branch, value) { if (!branch) { return null; } else if (branch.value === value) { return branch.data; } else if (branch.value > value) { return recGet(branch.left, value); } else if (branch.value < value) { return recGet(branch.right, value); } } function recUpdate(branch, value, oldData, newData) { if (!branch) { return branch; } else if (branch.value === value) { branch.data = branch.data.map((it) => (eq(it, oldData)) ? newData : it); return branch; } else if (branch.value > value) { return recUpdate(branch.left, value, oldData, newData); } else if (branch.value < value) { return recUpdate(branch.right, value, oldData, newData); } } function recRangeQuery(branch, fromValue, toValue, result) { if (!branch) { return result; } if (fromValue < branch.value) { recRangeQuery(branch.left, fromValue, toValue, result); } if (fromValue <= branch.value && toValue >= branch.value) { result.push(vec([branch.value, vec(branch.data)])) } if (toValue > branch.value) { recRangeQuery(branch.right, fromValue, toValue, result); } return result; } function rotateLeft(branch) { const x = branch.right; branch.right = x.left; x.left = branch; x.color = x.left.color; x.left.color = Color.RED; return x; } function rotateRight(branch) { const x = branch.left; branch.left = x.right; x.right = branch; x.color = x.right.color; x.right.color = Color.RED; return x; } function balance(branch) { if (isRed(branch.right)) { branch = rotateLeft(branch); } if (isRed(branch.left) && isRed(branch.left.left)) { branch = rotateRight(branch); } if (isRed(branch.left) && isRed(branch.right)) { flipColors(branch); } return branch; } function moveRedLeft(branch) { flipColors(branch); if (branch.right && isRed(branch.right.left)) { branch.right = rotateRight(branch.right); branch = rotateLeft(branch); flipColors(branch); } return branch; } function moveRedRight(branch) { flipColors(branch); if (branch.left && isRed(branch.left.left)) { branch = rotateRight(branch); flipColors(branch); } return branch; } function flip(color) { return color === Color.RED ? Color.BLACK : Color.RED; } function flipColors(branch) { branch.color = flip(branch.color); if (branch.left) { branch.left.color = flip(branch.left.color); } if (branch.right) { branch.right.color = flip(branch.right.color); } } function recHeight(branch) { let curHeight = 0; if (branch !== null) { curHeight = Math.max(recHeight(branch.left), recHeight(branch.right)) } return 1 + curHeight; } // This will return the string representation. We don't care about internal structure // only the data function recToString(branch, result) { if (!branch) { return; } recToString(branch.left, result); result.push(`${branch.value}: [${branch.data.join(", ")}]`) recToString(branch.right, result); } // This function prints the tree structure, not the data function printTree(tree) { if (!tree) { return ""; } const val = tree.color[0] + "(" + tree.value + ")"; return "[" + printTree(tree.left) + " " + val + " " + printTree(tree.right) + "]"; } function recTreeAsMap(branch, result) { if (!branch) { return result; } recTreeAsMap(branch.left, result); result[branch.value] = branch.data; recTreeAsMap(branch.right, result); return result; } // External API to CLJS const self = app.util.range_tree; self.make_tree = () => new RangeTree(); self.insert = (tree, value, data) => tree.insert(value, data); self.remove = (tree, value, data) => tree.remove(value, data); self.update = (tree, value, oldData, newData) => tree.update(value, oldData, newData); self.get = (tree, value) => { const result = tree.get(value); if (!result) { return nil; } return vec(result); }; self.range_query = (tree, from_value, to_value) => { if (!tree) { return vec(); } return vec(tree.rangeQuery(from_value, to_value)) }; self.empty_QMARK_ = (tree) => tree.isEmpty(); self.height = (tree) => tree.height(); self.print = (tree) => printTree(tree.root); self.as_map = (tree) => tree.asMap(); });