| /* |
| * Copyright (C) 2010, 2011, 2015 Apple Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * 3. Neither the name of Apple Inc. ("Apple") nor the names of |
| * its contributors may be used to endorse or promote products derived |
| * from this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY |
| * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
| * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY |
| * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
| * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
| * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
| * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
| * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| var RedBlackTree = (function(){ |
| function compare(a, b) { |
| return a.compareTo(b); |
| } |
| |
| function treeMinimum(x) { |
| while (x.left) |
| x = x.left; |
| return x; |
| } |
| |
| function treeMaximum(x) { |
| while (x.right) |
| x = x.right; |
| return x; |
| } |
| |
| function Node(key, value) { |
| this.key = key; |
| this.value = value; |
| this.left = null; |
| this.right = null; |
| this.parent = null; |
| this.color = "red"; |
| } |
| |
| Node.prototype.successor = function() { |
| var x = this; |
| if (x.right) |
| return treeMinimum(x.right); |
| var y = x.parent; |
| while (y && x == y.right) { |
| x = y; |
| y = y.parent; |
| } |
| return y; |
| }; |
| |
| Node.prototype.predecessor = function() { |
| var x = this; |
| if (x.left) |
| return treeMaximum(x.left); |
| var y = x.parent; |
| while (y && x == y.left) { |
| x = y; |
| y = y.parent; |
| } |
| return y; |
| }; |
| |
| Node.prototype.toString = function() { |
| return this.key + "=>" + this.value + " (" + this.color + ")"; |
| }; |
| |
| function RedBlackTree() { |
| this._root = null; |
| } |
| |
| RedBlackTree.prototype.put = function(key, value) { |
| var insertionResult = this._treeInsert(key, value); |
| if (!insertionResult.isNewEntry) |
| return insertionResult.oldValue; |
| var x = insertionResult.newNode; |
| |
| while (x != this._root && x.parent.color == "red") { |
| if (x.parent == x.parent.parent.left) { |
| var y = x.parent.parent.right; |
| if (y && y.color == "red") { |
| // Case 1 |
| x.parent.color = "black"; |
| y.color = "black"; |
| x.parent.parent.color = "red"; |
| x = x.parent.parent; |
| } else { |
| if (x == x.parent.right) { |
| // Case 2 |
| x = x.parent; |
| this._leftRotate(x); |
| } |
| // Case 3 |
| x.parent.color = "black"; |
| x.parent.parent.color = "red"; |
| this._rightRotate(x.parent.parent); |
| } |
| } else { |
| // Same as "then" clause with "right" and "left" exchanged. |
| var y = x.parent.parent.left; |
| if (y && y.color == "red") { |
| // Case 1 |
| x.parent.color = "black"; |
| y.color = "black"; |
| x.parent.parent.color = "red"; |
| x = x.parent.parent; |
| } else { |
| if (x == x.parent.left) { |
| // Case 2 |
| x = x.parent; |
| this._rightRotate(x); |
| } |
| // Case 3 |
| x.parent.color = "black"; |
| x.parent.parent.color = "red"; |
| this._leftRotate(x.parent.parent); |
| } |
| } |
| } |
| |
| this._root.color = "black"; |
| return null; |
| }; |
| |
| RedBlackTree.prototype.remove = function(key) { |
| var z = this._findNode(key); |
| if (!z) |
| return null; |
| |
| // Y is the node to be unlinked from the tree. |
| var y; |
| if (!z.left || !z.right) |
| y = z; |
| else |
| y = z.successor(); |
| |
| // Y is guaranteed to be non-null at this point. |
| var x; |
| if (y.left) |
| x = y.left; |
| else |
| x = y.right; |
| |
| // X is the child of y which might potentially replace y in the tree. X might be null at |
| // this point. |
| var xParent; |
| if (x) { |
| x.parent = y.parent; |
| xParent = x.parent; |
| } else |
| xParent = y.parent; |
| if (!y.parent) |
| this._root = x; |
| else { |
| if (y == y.parent.left) |
| y.parent.left = x; |
| else |
| y.parent.right = x; |
| } |
| |
| if (y != z) { |
| if (y.color == "black") |
| this._removeFixup(x, xParent); |
| |
| y.parent = z.parent; |
| y.color = z.color; |
| y.left = z.left; |
| y.right = z.right; |
| |
| if (z.left) |
| z.left.parent = y; |
| if (z.right) |
| z.right.parent = y; |
| if (z.parent) { |
| if (z.parent.left == z) |
| z.parent.left = y; |
| else |
| z.parent.right = y; |
| } else |
| this._root = y; |
| } else if (y.color == "black") |
| this._removeFixup(x, xParent); |
| |
| return z.value; |
| }; |
| |
| RedBlackTree.prototype.get = function(key) { |
| var node = this._findNode(key); |
| if (!node) |
| return null; |
| return node.value; |
| }; |
| |
| RedBlackTree.prototype.forEach = function(callback) { |
| if (!this._root) |
| return; |
| for (var current = treeMinimum(this._root); current; current = current.successor()) |
| callback(current.key, current.value); |
| }; |
| |
| RedBlackTree.prototype.asArray = function() { |
| var result = []; |
| this.forEach(function(key, value) { |
| result.push({key: key, value: value}); |
| }); |
| return result; |
| }; |
| |
| RedBlackTree.prototype.toString = function() { |
| var result = "["; |
| var first = true; |
| this.forEach(function(key, value) { |
| if (first) |
| first = false; |
| else |
| result += ", "; |
| result += key + "=>" + value; |
| }); |
| return result + "]"; |
| }; |
| |
| RedBlackTree.prototype._findNode = function(key) { |
| for (var current = this._root; current;) { |
| var comparisonResult = compare(key, current.key); |
| if (!comparisonResult) |
| return current; |
| if (comparisonResult < 0) |
| current = current.left; |
| else |
| current = current.right; |
| } |
| return null; |
| }; |
| |
| RedBlackTree.prototype._treeInsert = function(key, value) { |
| var y = null; |
| var x = this._root; |
| while (x) { |
| y = x; |
| var comparisonResult = key.compareTo(x.key); |
| if (comparisonResult < 0) |
| x = x.left; |
| else if (comparisonResult > 0) |
| x = x.right; |
| else { |
| var oldValue = x.value; |
| x.value = value; |
| return {isNewEntry:false, oldValue:oldValue}; |
| } |
| } |
| var z = new Node(key, value); |
| z.parent = y; |
| if (!y) |
| this._root = z; |
| else { |
| if (key.compareTo(y.key) < 0) |
| y.left = z; |
| else |
| y.right = z; |
| } |
| return {isNewEntry:true, newNode:z}; |
| }; |
| |
| RedBlackTree.prototype._leftRotate = function(x) { |
| var y = x.right; |
| |
| // Turn y's left subtree into x's right subtree. |
| x.right = y.left; |
| if (y.left) |
| y.left.parent = x; |
| |
| // Link x's parent to y. |
| y.parent = x.parent; |
| if (!x.parent) |
| this._root = y; |
| else { |
| if (x == x.parent.left) |
| x.parent.left = y; |
| else |
| x.parent.right = y; |
| } |
| |
| // Put x on y's left. |
| y.left = x; |
| x.parent = y; |
| |
| return y; |
| }; |
| |
| RedBlackTree.prototype._rightRotate = function(y) { |
| var x = y.left; |
| |
| // Turn x's right subtree into y's left subtree. |
| y.left = x.right; |
| if (x.right) |
| x.right.parent = y; |
| |
| // Link y's parent to x; |
| x.parent = y.parent; |
| if (!y.parent) |
| this._root = x; |
| else { |
| if (y == y.parent.left) |
| y.parent.left = x; |
| else |
| y.parent.right = x; |
| } |
| |
| x.right = y; |
| y.parent = x; |
| |
| return x; |
| }; |
| |
| RedBlackTree.prototype._removeFixup = function(x, xParent) { |
| while (x != this._root && (!x || x.color == "black")) { |
| if (x == xParent.left) { |
| // Note: the text points out that w cannot be null. The reason is not obvious from |
| // simply looking at the code; it comes about from the properties of the red-black |
| // tree. |
| var w = xParent.right; |
| if (w.color == "red") { |
| // Case 1 |
| w.color = "black"; |
| xParent.color = "red"; |
| this._leftRotate(xParent); |
| w = xParent.right; |
| } |
| if ((!w.left || w.left.color == "black") |
| && (!w.right || w.right.color == "black")) { |
| // Case 2 |
| w.color = "red"; |
| x = xParent; |
| xParent = x.parent; |
| } else { |
| if (!w.right || w.right.color == "black") { |
| // Case 3 |
| w.left.color = "black"; |
| w.color = "red"; |
| this._rightRotate(w); |
| w = xParent.right; |
| } |
| // Case 4 |
| w.color = xParent.color; |
| xParent.color = "black"; |
| if (w.right) |
| w.right.color = "black"; |
| this._leftRotate(xParent); |
| x = this._root; |
| xParent = x.parent; |
| } |
| } else { |
| // Same as "then" clause with "right" and "left" exchanged. |
| |
| var w = xParent.left; |
| if (w.color == "red") { |
| // Case 1 |
| w.color = "black"; |
| xParent.color = "red"; |
| this._rightRotate(xParent); |
| w = xParent.left; |
| } |
| if ((!w.right || w.right.color == "black") |
| && (!w.left || w.left.color == "black")) { |
| // Case 2 |
| w.color = "red"; |
| x = xParent; |
| xParent = x.parent; |
| } else { |
| if (!w.left || w.left.color == "black") { |
| // Case 3 |
| w.right.color = "black"; |
| w.color = "red"; |
| this._leftRotate(w); |
| w = xParent.left; |
| } |
| // Case 4 |
| w.color = xParent.color; |
| xParent.color = "black"; |
| if (w.left) |
| w.left.color = "black"; |
| this._rightRotate(xParent); |
| x = this._root; |
| xParent = x.parent; |
| } |
| } |
| } |
| if (x) |
| x.color = "black"; |
| }; |
| |
| return RedBlackTree; |
| })(); |
| |