From 3653b244764503bdadab3b0b59e5c16b78f9b51e Mon Sep 17 00:00:00 2001 From: Danny Yoo Date: Thu, 3 Nov 2011 12:31:49 -0400 Subject: [PATCH] avltree libraries might be useful for immutable hash tables. --- js-assembler/runtime-src/avltree.js | 772 ++++++++++++++++++++++++++++ scribblings/manual.scrbl | 1 + 2 files changed, 773 insertions(+) create mode 100644 js-assembler/runtime-src/avltree.js diff --git a/js-assembler/runtime-src/avltree.js b/js-assembler/runtime-src/avltree.js new file mode 100644 index 0000000..c15315d --- /dev/null +++ b/js-assembler/runtime-src/avltree.js @@ -0,0 +1,772 @@ +// ---------------------------------------------------------------------- +// dyoo: the following code comes from the Google Closure Library. I've done +// light edits to flatten the namespace from goog.structs to just +// AvlTree. +// ---------------------------------------------------------------------- + + +// Copyright 2007 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Datastructure: AvlTree. + * + * + * This file provides the implementation of an AVL-Tree datastructure. The tree + * maintains a set of unique values in a sorted order. The values can be + * accessed efficiently in their sorted order since the tree enforces an O(logn) + * maximum height. See http://en.wikipedia.org/wiki/Avl_tree for more detail. + * + * The big-O notation for all operations are below: + *
+ *   Method                 big-O
+ * ----------------------------------------------------------------------------
+ * - add                    O(logn)
+ * - remove                 O(logn)
+ * - clear                  O(1)
+ * - contains               O(logn)
+ * - getCount               O(1)
+ * - getMinimum             O(1), or O(logn) when optional root is specified
+ * - getMaximum             O(1), or O(logn) when optional root is specified
+ * - getHeight              O(1)
+ * - getValues              O(n)
+ * - inOrderTraverse        O(logn + k), where k is number of traversed nodes
+ * - reverseOrderTraverse   O(logn + k), where k is number of traversed nodes
+ * 
+ */ + + + +/** + * Constructs an AVL-Tree, which uses the specified comparator to order its + * values. The values can be accessed efficiently in their sorted order since + * the tree enforces a O(logn) maximum height. + * + * @param {Function=} opt_comparator Function used to order the tree's nodes. + * @constructor + * @implements {Collection} + */ +var AvlTree = function(opt_comparator) { + this.comparator_ = opt_comparator || + AvlTree.DEFAULT_COMPARATOR_; +}; + + +/** + * String comparison function used to compare values in the tree. This function + * is used by default if no comparator is specified in the tree's constructor. + * + * @param {string} a The first string. + * @param {string} b The second string. + * @return {number} -1 if a < b, 1 if a > b, 0 if a = b. + * @private + */ +AvlTree.DEFAULT_COMPARATOR_ = function(a, b) { + if (String(a) < String(b)) { + return -1; + } else if (String(a) > String(b)) { + return 1; + } + return 0; +}; + + +/** + * Pointer to the root node of the tree. + * + * @type {AvlTree.Node} + * @private + */ +AvlTree.prototype.root_ = null; + + +/** + * Comparison function used to compare values in the tree. This function should + * take two values, a and b, and return x where: + *
+ *  x < 0 if a < b,
+ *  x > 0 if a > b,
+ *  x = 0 otherwise
+ * 
+ * + * @type {Function} + * @private + */ +AvlTree.prototype.comparator_ = null; + + +/** + * Pointer to the node with the smallest value in the tree. + * + * @type {AvlTree.Node} + * @private + */ +AvlTree.prototype.minNode_ = null; + + +/** + * Pointer to the node with the largest value in the tree. + * + * @type {AvlTree.Node} + * @private + */ +AvlTree.prototype.maxNode_ = null; + + +/** + * Keeps track of the number of nodes in the tree. + * + * @type {number} + * @private + */ +AvlTree.prototype.count_ = 0; + + +/** + * Inserts a node into the tree with the specified value if the tree does + * not already contain a node with the specified value. If the value is + * inserted, the tree is balanced to enforce the AVL-Tree height property. + * + * @param {*} value Value to insert into the tree. + * @return {boolean} Whether value was inserted into the tree. + */ +AvlTree.prototype.add = function(value) { + // If the tree is empty, create a root node with the specified value + if (this.root_ == null) { + this.root_ = new AvlTree.Node(value); + this.minNode_ = this.root_; + this.maxNode_ = this.root_; + this.count_ = 1; + return true; + } + + // Assume a node is not added and change status when one is + var retStatus = false; + + // Depth traverse the tree and insert the value if we reach a null node + this.traverse_(function(node) { + var retNode = null; + if (this.comparator_(node.value, value) > 0) { + retNode = node.left; + if (node.left == null) { + var newNode = new AvlTree.Node(value, node); + node.left = newNode; + if (node == this.minNode_) { + this.minNode_ = newNode; + } + retStatus = true; // Value was added to tree + this.balance_(node); // Maintain the AVL-tree balance + } + } else if (this.comparator_(node.value, value) < 0) { + retNode = node.right; + if (node.right == null) { + var newNode = new AvlTree.Node(value, node); + node.right = newNode; + if (node == this.maxNode_) { + this.maxNode_ = newNode; + } + retStatus = true; // Value was added to tree + this.balance_(node); // Maintain the AVL-tree balance + } + } + return retNode; // If null, we'll stop traversing the tree + }); + + // If a node was added, increment count + if (retStatus) { + this.count_ += 1; + } + + // Return true if a node was added, false otherwise + return retStatus; +}; + + +/** + * Removes a node from the tree with the specified value if the tree contains a + * node with this value. If a node is removed the tree is balanced to enforce + * the AVL-Tree height property. The value of the removed node is returned. + * + * @param {*} value Value to find and remove from the tree. + * @return {*} The value of the removed node or null if the value was not in + * the tree. + */ +AvlTree.prototype.remove = function(value) { + // Assume the value is not removed and set the value when it is removed + var retValue = null; + + // Depth traverse the tree and remove the value if we find it + this.traverse_(function(node) { + var retNode = null; + if (this.comparator_(node.value, value) > 0) { + retNode = node.left; + } else if (this.comparator_(node.value, value) < 0) { + retNode = node.right; + } else { + retValue = node.value; + this.removeNode_(node); + } + return retNode; // If null, we'll stop traversing the tree + }); + + // If a node was removed, decrement count. + if (retValue) { + // Had traverse_() cleared the tree, set to 0. + this.count_ = this.root_ ? this.count_ - 1 : 0; + } + + // Return the value that was removed, null if the value was not in the tree + return retValue; +}; + + +/** + * Removes all nodes from the tree. + */ +AvlTree.prototype.clear = function() { + this.root_ = null; + this.minNode_ = null; + this.maxNode_ = null; + this.count_ = 0; +}; + + +/** + * Returns true if the tree contains a node with the specified value, false + * otherwise. + * + * @param {*} value Value to find in the tree. + * @return {boolean} Whether the tree contains a node with the specified value. + */ +AvlTree.prototype.contains = function(value) { + // Assume the value is not in the tree and set this value if it is found + var isContained = false; + + // Depth traverse the tree and set isContained if we find the node + this.traverse_(function(node) { + var retNode = null; + if (this.comparator_(node.value, value) > 0) { + retNode = node.left; + } else if (this.comparator_(node.value, value) < 0) { + retNode = node.right; + } else { + isContained = true; + } + return retNode; // If null, we'll stop traversing the tree + }); + + // Return true if the value is contained in the tree, false otherwise + return isContained; +}; + + +/** + * Returns the number of values stored in the tree. + * + * @return {number} The number of values stored in the tree. + */ +AvlTree.prototype.getCount = function() { + return this.count_; +}; + + +/** + * Returns the value u, such that u is contained in the tree and u < v, for all + * values v in the tree where v != u. + * + * @return {*} The minimum value contained in the tree. + */ +AvlTree.prototype.getMinimum = function() { + return this.getMinNode_().value; +}; + + +/** + * Returns the value u, such that u is contained in the tree and u > v, for all + * values v in the tree where v != u. + * + * @return {*} The maximum value contained in the tree. + */ +AvlTree.prototype.getMaximum = function() { + return this.getMaxNode_().value; +}; + + +/** + * Returns the height of the tree (the maximum depth). This height should + * always be <= 1.4405*(Math.log(n+2)/Math.log(2))-1.3277, where n is the + * number of nodes in the tree. + * + * @return {number} The height of the tree. + */ +AvlTree.prototype.getHeight = function() { + return this.root_ ? this.root_.height : 0; +}; + + +/** + * Inserts the values stored in the tree into a new Array and returns the Array. + * + * @return {Array} An array containing all of the trees values in sorted order. + */ +AvlTree.prototype.getValues = function() { + var ret = []; + this.inOrderTraverse(function(value) { + ret.push(value); + }); + return ret; +}; + + +/** + * Performs an in-order traversal of the tree and calls {@code func} with each + * traversed node, optionally starting from the smallest node with a value >= to + * the specified start value. The traversal ends after traversing the tree's + * maximum node or when {@code func} returns a value that evaluates to true. + * + * @param {Function} func Function to call on each traversed node. + * @param {Object=} opt_startValue If specified, traversal will begin on the + * node with the smallest value >= opt_startValue. + */ +AvlTree.prototype.inOrderTraverse = + function(func, opt_startValue) { + // If our tree is empty, return immediately + if (!this.root_) { + return; + } + + // Depth traverse the tree to find node to begin in-order traversal from + var startNode; + if (opt_startValue) { + this.traverse_(function(node) { + var retNode = null; + if (this.comparator_(node.value, opt_startValue) > 0) { + retNode = node.left; + startNode = node; + } else if (this.comparator_(node.value, opt_startValue) < 0) { + retNode = node.right; + } else { + startNode = node; + } + return retNode; // If null, we'll stop traversing the tree + }); + } else { + startNode = this.getMinNode_(); + } + + // Traverse the tree and call func on each traversed node's value + var node = startNode, prev = startNode.left ? startNode.left : startNode; + while (node != null) { + if (node.left != null && node.left != prev && node.right != prev) { + node = node.left; + } else { + if (node.right != prev) { + if (func(node.value)) { + return; + } + } + var temp = node; + node = node.right != null && node.right != prev ? + node.right : + node.parent; + prev = temp; + } + } +}; + + +/** + * Performs a reverse-order traversal of the tree and calls {@code func} with + * each traversed node, optionally starting from the largest node with a value + * <= to the specified start value. The traversal ends after traversing the + * tree's minimum node or when func returns a value that evaluates to true. + * + * @param {Function} func Function to call on each traversed node. + * @param {Object=} opt_startValue If specified, traversal will begin on the + * node with the largest value <= opt_startValue. + */ +AvlTree.prototype.reverseOrderTraverse = + function(func, opt_startValue) { + // If our tree is empty, return immediately + if (!this.root_) { + return; + } + + // Depth traverse the tree to find node to begin reverse-order traversal from + var startNode; + if (opt_startValue) { + this.traverse_(goog.bind(function(node) { + var retNode = null; + if (this.comparator_(node.value, opt_startValue) > 0) { + retNode = node.left; + } else if (this.comparator_(node.value, opt_startValue) < 0) { + retNode = node.right; + startNode = node; + } else { + startNode = node; + } + return retNode; // If null, we'll stop traversing the tree + }, this)); + } else { + startNode = this.getMaxNode_(); + } + + // Traverse the tree and call func on each traversed node's value + var node = startNode, prev = startNode.right ? startNode.right : startNode; + while (node != null) { + if (node.right != null && node.right != prev && node.left != prev) { + node = node.right; + } else { + if (node.left != prev) { + if (func(node.value)) { + return; + } + } + var temp = node; + node = node.left != null && node.left != prev ? + node.left : + node.parent; + prev = temp; + } + } +}; + + +/** + * Performs a traversal defined by the supplied {@code traversalFunc}. The first + * call to {@code traversalFunc} is passed the root or the optionally specified + * startNode. After that, calls {@code traversalFunc} with the node returned + * by the previous call to {@code traversalFunc} until {@code traversalFunc} + * returns null or the optionally specified endNode. The first call to + * traversalFunc is passed the root or the optionally specified startNode. + * + * @param {Function} traversalFunc Function used to traverse the tree. Takes a + * node as a parameter and returns a node. + * @param {AvlTree.Node=} opt_startNode The node at which the + * traversal begins. + * @param {AvlTree.Node=} opt_endNode The node at which the + * traversal ends. + * @private + */ +AvlTree.prototype.traverse_ = + function(traversalFunc, opt_startNode, opt_endNode) { + var node = opt_startNode ? opt_startNode : this.root_; + var endNode = opt_endNode ? opt_endNode : null; + while (node && node != endNode) { + node = traversalFunc.call(this, node); + } +}; + + +/** + * Ensures that the specified node and all its ancestors are balanced. If they + * are not, performs left and right tree rotations to achieve a balanced + * tree. This method assumes that at most 2 rotations are necessary to balance + * the tree (which is true for AVL-trees that are balanced after each node is + * added or removed). + * + * @param {AvlTree.Node} node Node to begin balance from. + * @private + */ +AvlTree.prototype.balance_ = function(node) { + + this.traverse_(function(node) { + // Calculate the left and right node's heights + var lh = node.left ? node.left.height : 0; + var rh = node.right ? node.right.height : 0; + + // Rotate tree rooted at this node if it is not AVL-tree balanced + if (lh - rh > 1) { + if (node.left.right && (!node.left.left || + node.left.left.height < node.left.right.height)) { + this.leftRotate_(node.left); + } + this.rightRotate_(node); + } else if (rh - lh > 1) { + if (node.right.left && (!node.right.right || + node.right.right.height < node.right.left.height)) { + this.rightRotate_(node.right); + } + this.leftRotate_(node); + } + + // Recalculate the left and right node's heights + lh = node.left ? node.left.height : 0; + rh = node.right ? node.right.height : 0; + + // Set this node's height + node.height = Math.max(lh, rh) + 1; + + // Traverse up tree and balance parent + return node.parent; + }, node); + +}; + + +/** + * Performs a left tree rotation on the specified node. + * + * @param {AvlTree.Node} node Pivot node to rotate from. + * @private + */ +AvlTree.prototype.leftRotate_ = function(node) { + // Re-assign parent-child references for the parent of the node being removed + if (node.isLeftChild()) { + node.parent.left = node.right; + node.right.parent = node.parent; + } else if (node.isRightChild()) { + node.parent.right = node.right; + node.right.parent = node.parent; + } else { + this.root_ = node.right; + this.root_.parent = null; + } + + // Re-assign parent-child references for the child of the node being removed + var temp = node.right; + node.right = node.right.left; + if (node.right != null) node.right.parent = node; + temp.left = node; + node.parent = temp; +}; + + +/** + * Performs a right tree rotation on the specified node. + * + * @param {AvlTree.Node} node Pivot node to rotate from. + * @private + */ +AvlTree.prototype.rightRotate_ = function(node) { + // Re-assign parent-child references for the parent of the node being removed + if (node.isLeftChild()) { + node.parent.left = node.left; + node.left.parent = node.parent; + } else if (node.isRightChild()) { + node.parent.right = node.left; + node.left.parent = node.parent; + } else { + this.root_ = node.left; + this.root_.parent = null; + } + + // Re-assign parent-child references for the child of the node being removed + var temp = node.left; + node.left = node.left.right; + if (node.left != null) node.left.parent = node; + temp.right = node; + node.parent = temp; +}; + + +/** + * Removes the specified node from the tree and ensures the tree still + * maintains the AVL-tree balance. + * + * @param {AvlTree.Node} node The node to be removed. + * @private + */ +AvlTree.prototype.removeNode_ = function(node) { + // Perform normal binary tree node removal, but balance the tree, starting + // from where we removed the node + if (node.left != null || node.right != null) { + var b = null; // Node to begin balance from + var r; // Node to replace the node being removed + if (node.left != null) { + r = this.getMaxNode_(node.left); + if (r != node.left) { + r.parent.right = r.left; + if (r.left) r.left.parent = r.parent; + r.left = node.left; + r.left.parent = r; + b = r.parent; + } + r.parent = node.parent; + r.right = node.right; + if (r.right) r.right.parent = r; + if (node == this.maxNode_) this.maxNode_ = r; + } else { + r = this.getMinNode_(node.right); + if (r != node.right) { + r.parent.left = r.right; + if (r.right) r.right.parent = r.parent; + r.right = node.right; + r.right.parent = r; + b = r.parent; + } + r.parent = node.parent; + r.left = node.left; + if (r.left) r.left.parent = r; + if (node == this.minNode_) this.minNode_ = r; + } + + // Update the parent of the node being removed to point to its replace + if (node.isLeftChild()) { + node.parent.left = r; + } else if (node.isRightChild()) { + node.parent.right = r; + } else { + this.root_ = r; + } + + // Balance the tree + this.balance_(b ? b : r); + } else { + // If the node is a leaf, remove it and balance starting from its parent + if (node.isLeftChild()) { + this.special = 1; + node.parent.left = null; + if (node == this.minNode_) this.minNode_ = node.parent; + this.balance_(node.parent); + } else if (node.isRightChild()) { + node.parent.right = null; + if (node == this.maxNode_) this.maxNode_ = node.parent; + this.balance_(node.parent); + } else { + this.clear(); + } + } +}; + + +/** + * Returns the node with the smallest value in tree, optionally rooted at + * {@code opt_rootNode}. + * + * @param {AvlTree.Node=} opt_rootNode Optional root node. + * @return {AvlTree.Node} The node with the smallest value in + * the tree. + * @private + */ +AvlTree.prototype.getMinNode_ = function(opt_rootNode) { + if (!opt_rootNode) { + return this.minNode_; + } + + var minNode = opt_rootNode; + this.traverse_(function(node) { + var retNode = null; + if (node.left) { + minNode = node.left; + retNode = node.left; + } + return retNode; // If null, we'll stop traversing the tree + }, opt_rootNode); + + return minNode; +}; + + +/** + * Returns the node with the largest value in tree, optionally rooted at + * opt_rootNode. + * + * @param {AvlTree.Node=} opt_rootNode Optional root node. + * @return {AvlTree.Node} The node with the largest value in + * the tree. + * @private + */ +AvlTree.prototype.getMaxNode_ = function(opt_rootNode) { + if (!opt_rootNode) { + return this.maxNode_; + } + + var maxNode = opt_rootNode; + this.traverse_(function(node) { + var retNode = null; + if (node.right) { + maxNode = node.right; + retNode = node.right; + } + return retNode; // If null, we'll stop traversing the tree + }, opt_rootNode); + + return maxNode; +}; + + + +/** + * Constructs an AVL-Tree node with the specified value. If no parent is + * specified, the node's parent is assumed to be null. The node's height + * defaults to 1 and its children default to null. + * + * @param {*} value Value to store in the node. + * @param {AvlTree.Node=} opt_parent Optional parent node. + * @constructor + */ +AvlTree.Node = function(value, opt_parent) { + /** + * The value stored by the node. + * + * @type {*} + */ + this.value = value; + + /** + * The node's parent. Null if the node is the root. + * + * @type {AvlTree.Node} + */ + this.parent = opt_parent ? opt_parent : null; +}; + + +/** + * The node's left child. Null if the node does not have a left child. + * + * @type {AvlTree.Node?} + */ +AvlTree.Node.prototype.left = null; + + +/** + * The node's right child. Null if the node does not have a right child. + * + * @type {AvlTree.Node?} + */ +AvlTree.Node.prototype.right = null; + + +/** + * The height of the tree rooted at this node. + * + * @type {number} + */ +AvlTree.Node.prototype.height = 1; + + +/** + * Returns true iff the specified node has a parent and is the right child of + * its parent. + * + * @return {boolean} Whether the specified node has a parent and is the right + * child of its parent. + */ +AvlTree.Node.prototype.isRightChild = function() { + return !!this.parent && this.parent.right == this; +}; + + +/** + * Returns true iff the specified node has a parent and is the left child of + * its parent. + * + * @return {boolean} Whether the specified node has a parent and is the left + * child of its parent. + */ +AvlTree.Node.prototype.isLeftChild = function() { + return !!this.parent && this.parent.left == this; +}; \ No newline at end of file diff --git a/scribblings/manual.scrbl b/scribblings/manual.scrbl index df28f09..71f8405 100644 --- a/scribblings/manual.scrbl +++ b/scribblings/manual.scrbl @@ -1231,6 +1231,7 @@ Whalesong uses code and utilities from the following external projects: @item{JSON (@url{http://www.json.org/js.html})} @item{jquery (@url{http://jquery.com/})} @item{Google Closure Compiler (@url{http://code.google.com/p/closure-compiler/})} +@item{Google Closure Library (@url{http://code.google.com/closure/library/}) (avltree.js)} @item{excanvas (@url{http://excanvas.sourceforge.net/})}