Adding mapbox-gl branch
This commit is contained in:
@@ -0,0 +1,899 @@
|
||||
// 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:
|
||||
* <pre>
|
||||
* Method big-O
|
||||
* ----------------------------------------------------------------------------
|
||||
* - add O(logn)
|
||||
* - remove O(logn)
|
||||
* - clear O(1)
|
||||
* - contains O(logn)
|
||||
* - indexOf 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
|
||||
* </pre>
|
||||
*/
|
||||
|
||||
|
||||
goog.provide('goog.structs.AvlTree');
|
||||
goog.provide('goog.structs.AvlTree.Node');
|
||||
|
||||
goog.require('goog.structs.Collection');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 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 {goog.structs.Collection<T>}
|
||||
* @final
|
||||
* @template T
|
||||
*/
|
||||
goog.structs.AvlTree = function(opt_comparator) {
|
||||
this.comparator_ = opt_comparator ||
|
||||
goog.structs.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 {T} a The first value.
|
||||
* @param {T} b The second value.
|
||||
* @return {number} -1 if a < b, 1 if a > b, 0 if a = b.
|
||||
* @template T
|
||||
* @private
|
||||
*/
|
||||
goog.structs.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.
|
||||
*
|
||||
* @private {goog.structs.AvlTree.Node<T>}
|
||||
*/
|
||||
goog.structs.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:
|
||||
* <pre>
|
||||
* x < 0 if a < b,
|
||||
* x > 0 if a > b,
|
||||
* x = 0 otherwise
|
||||
* </pre>
|
||||
*
|
||||
* @type {Function}
|
||||
* @private
|
||||
*/
|
||||
goog.structs.AvlTree.prototype.comparator_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* Pointer to the node with the smallest value in the tree.
|
||||
*
|
||||
* @type {goog.structs.AvlTree.Node<T>}
|
||||
* @private
|
||||
*/
|
||||
goog.structs.AvlTree.prototype.minNode_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* Pointer to the node with the largest value in the tree.
|
||||
*
|
||||
* @type {goog.structs.AvlTree.Node<T>}
|
||||
* @private
|
||||
*/
|
||||
goog.structs.AvlTree.prototype.maxNode_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* 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 {T} value Value to insert into the tree.
|
||||
* @return {boolean} Whether value was inserted into the tree.
|
||||
* @override
|
||||
*/
|
||||
goog.structs.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 goog.structs.AvlTree.Node(value);
|
||||
this.minNode_ = this.root_;
|
||||
this.maxNode_ = this.root_;
|
||||
return true;
|
||||
}
|
||||
|
||||
// This will be set to the new node if a new node is added.
|
||||
var newNode = null;
|
||||
|
||||
// Depth traverse the tree and insert the value if we reach a null node
|
||||
this.traverse_(function(node) {
|
||||
var retNode = null;
|
||||
var comparison = this.comparator_(node.value, value);
|
||||
if (comparison > 0) {
|
||||
retNode = node.left;
|
||||
if (node.left == null) {
|
||||
newNode = new goog.structs.AvlTree.Node(value, node);
|
||||
node.left = newNode;
|
||||
if (node == this.minNode_) {
|
||||
this.minNode_ = newNode;
|
||||
}
|
||||
}
|
||||
} else if (comparison < 0) {
|
||||
retNode = node.right;
|
||||
if (node.right == null) {
|
||||
newNode = new goog.structs.AvlTree.Node(value, node);
|
||||
node.right = newNode;
|
||||
if (node == this.maxNode_) {
|
||||
this.maxNode_ = newNode;
|
||||
}
|
||||
}
|
||||
}
|
||||
return retNode; // If null, we'll stop traversing the tree
|
||||
});
|
||||
|
||||
// If a node was added, increment counts and balance tree.
|
||||
if (newNode) {
|
||||
this.traverse_(
|
||||
function(node) {
|
||||
node.count++;
|
||||
return node.parent;
|
||||
},
|
||||
newNode.parent);
|
||||
this.balance_(newNode.parent); // Maintain the AVL-tree balance
|
||||
}
|
||||
|
||||
// Return true if a node was added, false otherwise
|
||||
return !!newNode;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* 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 {T} value Value to find and remove from the tree.
|
||||
* @return {T} The value of the removed node or null if the value was not in
|
||||
* the tree.
|
||||
* @override
|
||||
*/
|
||||
goog.structs.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;
|
||||
var comparison = this.comparator_(node.value, value);
|
||||
if (comparison > 0) {
|
||||
retNode = node.left;
|
||||
} else if (comparison < 0) {
|
||||
retNode = node.right;
|
||||
} else {
|
||||
retValue = node.value;
|
||||
this.removeNode_(node);
|
||||
}
|
||||
return retNode; // If null, we'll stop traversing the tree
|
||||
});
|
||||
|
||||
// Return the value that was removed, null if the value was not in the tree
|
||||
return retValue;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Removes all nodes from the tree.
|
||||
*/
|
||||
goog.structs.AvlTree.prototype.clear = function() {
|
||||
this.root_ = null;
|
||||
this.minNode_ = null;
|
||||
this.maxNode_ = null;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns true if the tree contains a node with the specified value, false
|
||||
* otherwise.
|
||||
*
|
||||
* @param {T} value Value to find in the tree.
|
||||
* @return {boolean} Whether the tree contains a node with the specified value.
|
||||
* @override
|
||||
*/
|
||||
goog.structs.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;
|
||||
var comparison = this.comparator_(node.value, value);
|
||||
if (comparison > 0) {
|
||||
retNode = node.left;
|
||||
} else if (comparison < 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 index (in an in-order traversal) of the node in the tree with
|
||||
* the specified value. For example, the minimum value in the tree will
|
||||
* return an index of 0 and the maximum will return an index of n - 1 (where
|
||||
* n is the number of nodes in the tree). If the value is not found then -1
|
||||
* is returned.
|
||||
*
|
||||
* @param {T} value Value in the tree whose in-order index is returned.
|
||||
* @return {!number} The in-order index of the given value in the
|
||||
* tree or -1 if the value is not found.
|
||||
*/
|
||||
goog.structs.AvlTree.prototype.indexOf = function(value) {
|
||||
// Assume the value is not in the tree and set this value if it is found
|
||||
var retIndex = -1;
|
||||
var currIndex = 0;
|
||||
|
||||
// Depth traverse the tree and set retIndex if we find the node
|
||||
this.traverse_(function(node) {
|
||||
var comparison = this.comparator_(node.value, value);
|
||||
if (comparison > 0) {
|
||||
// The value is less than this node, so recurse into the left subtree.
|
||||
return node.left;
|
||||
}
|
||||
|
||||
if (node.left) {
|
||||
// The value is greater than all of the nodes in the left subtree.
|
||||
currIndex += node.left.count;
|
||||
}
|
||||
|
||||
if (comparison < 0) {
|
||||
// The value is also greater than this node.
|
||||
currIndex++;
|
||||
// Recurse into the right subtree.
|
||||
return node.right;
|
||||
}
|
||||
// We found the node, so stop traversing the tree.
|
||||
retIndex = currIndex;
|
||||
return null;
|
||||
});
|
||||
|
||||
// Return index if the value is contained in the tree, -1 otherwise
|
||||
return retIndex;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the number of values stored in the tree.
|
||||
*
|
||||
* @return {number} The number of values stored in the tree.
|
||||
* @override
|
||||
*/
|
||||
goog.structs.AvlTree.prototype.getCount = function() {
|
||||
return this.root_ ? this.root_.count : 0;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns a k-th smallest value, based on the comparator, where 0 <= k <
|
||||
* this.getCount().
|
||||
* @param {number} k The number k.
|
||||
* @return {T} The k-th smallest value.
|
||||
*/
|
||||
goog.structs.AvlTree.prototype.getKthValue = function(k) {
|
||||
if (k < 0 || k >= this.getCount()) {
|
||||
return null;
|
||||
}
|
||||
return this.getKthNode_(k).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 {T} The minimum value contained in the tree.
|
||||
*/
|
||||
goog.structs.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 {T} The maximum value contained in the tree.
|
||||
*/
|
||||
goog.structs.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.
|
||||
*/
|
||||
goog.structs.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<T>} An array containing all of the trees values in sorted
|
||||
* order.
|
||||
*/
|
||||
goog.structs.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.
|
||||
*/
|
||||
goog.structs.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;
|
||||
var comparison = this.comparator_(node.value, opt_startValue);
|
||||
if (comparison > 0) {
|
||||
retNode = node.left;
|
||||
startNode = node;
|
||||
} else if (comparison < 0) {
|
||||
retNode = node.right;
|
||||
} else {
|
||||
startNode = node;
|
||||
}
|
||||
return retNode; // If null, we'll stop traversing the tree
|
||||
});
|
||||
if (!startNode) {
|
||||
return;
|
||||
}
|
||||
} 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(T):?} 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.
|
||||
*/
|
||||
goog.structs.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;
|
||||
var comparison = this.comparator_(node.value, opt_startValue);
|
||||
if (comparison > 0) {
|
||||
retNode = node.left;
|
||||
} else if (comparison < 0) {
|
||||
retNode = node.right;
|
||||
startNode = node;
|
||||
} else {
|
||||
startNode = node;
|
||||
}
|
||||
return retNode; // If null, we'll stop traversing the tree
|
||||
}, this));
|
||||
if (!startNode) {
|
||||
return;
|
||||
}
|
||||
} 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(
|
||||
* this:goog.structs.AvlTree<T>,
|
||||
* !goog.structs.AvlTree.Node):?goog.structs.AvlTree.Node} traversalFunc
|
||||
* Function used to traverse the tree.
|
||||
* @param {goog.structs.AvlTree.Node<T>=} opt_startNode The node at which the
|
||||
* traversal begins.
|
||||
* @param {goog.structs.AvlTree.Node<T>=} opt_endNode The node at which the
|
||||
* traversal ends.
|
||||
* @private
|
||||
*/
|
||||
goog.structs.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 {goog.structs.AvlTree.Node<T>} node Node to begin balance from.
|
||||
* @private
|
||||
*/
|
||||
goog.structs.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 {goog.structs.AvlTree.Node<T>} node Pivot node to rotate from.
|
||||
* @private
|
||||
*/
|
||||
goog.structs.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;
|
||||
|
||||
// Update counts.
|
||||
temp.count = node.count;
|
||||
node.count -= (temp.right ? temp.right.count : 0) + 1;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Performs a right tree rotation on the specified node.
|
||||
*
|
||||
* @param {goog.structs.AvlTree.Node<T>} node Pivot node to rotate from.
|
||||
* @private
|
||||
*/
|
||||
goog.structs.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;
|
||||
|
||||
// Update counts.
|
||||
temp.count = node.count;
|
||||
node.count -= (temp.left ? temp.left.count : 0) + 1;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Removes the specified node from the tree and ensures the tree still
|
||||
* maintains the AVL-tree balance.
|
||||
*
|
||||
* @param {goog.structs.AvlTree.Node<T>} node The node to be removed.
|
||||
* @private
|
||||
*/
|
||||
goog.structs.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);
|
||||
|
||||
// Update counts.
|
||||
this.traverse_(function(node) {
|
||||
node.count--;
|
||||
return node.parent;
|
||||
}, r);
|
||||
|
||||
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;
|
||||
r.count = node.count;
|
||||
} else {
|
||||
r = this.getMinNode_(node.right);
|
||||
|
||||
// Update counts.
|
||||
this.traverse_(function(node) {
|
||||
node.count--;
|
||||
return node.parent;
|
||||
}, r);
|
||||
|
||||
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;
|
||||
r.count = node.count;
|
||||
}
|
||||
|
||||
// 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 {
|
||||
// Update counts.
|
||||
this.traverse_(function(node) {
|
||||
node.count--;
|
||||
return node.parent;
|
||||
}, node.parent);
|
||||
|
||||
// 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 in the tree that has k nodes before it in an in-order
|
||||
* traversal, optionally rooted at {@code opt_rootNode}.
|
||||
*
|
||||
* @param {number} k The number of nodes before the node to be returned in an
|
||||
* in-order traversal, where 0 <= k < root.count.
|
||||
* @param {goog.structs.AvlTree.Node<T>=} opt_rootNode Optional root node.
|
||||
* @return {goog.structs.AvlTree.Node<T>} The node at the specified index.
|
||||
* @private
|
||||
*/
|
||||
goog.structs.AvlTree.prototype.getKthNode_ = function(k, opt_rootNode) {
|
||||
var root = opt_rootNode || this.root_;
|
||||
var numNodesInLeftSubtree = root.left ? root.left.count : 0;
|
||||
|
||||
if (k < numNodesInLeftSubtree) {
|
||||
return this.getKthNode_(k, root.left);
|
||||
} else if (k == numNodesInLeftSubtree) {
|
||||
return root;
|
||||
} else {
|
||||
return this.getKthNode_(k - numNodesInLeftSubtree - 1, root.right);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the node with the smallest value in tree, optionally rooted at
|
||||
* {@code opt_rootNode}.
|
||||
*
|
||||
* @param {goog.structs.AvlTree.Node<T>=} opt_rootNode Optional root node.
|
||||
* @return {goog.structs.AvlTree.Node<T>} The node with the smallest value in
|
||||
* the tree.
|
||||
* @private
|
||||
*/
|
||||
goog.structs.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 {goog.structs.AvlTree.Node<T>=} opt_rootNode Optional root node.
|
||||
* @return {goog.structs.AvlTree.Node<T>} The node with the largest value in
|
||||
* the tree.
|
||||
* @private
|
||||
*/
|
||||
goog.structs.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 {T} value Value to store in the node.
|
||||
* @param {goog.structs.AvlTree.Node<T>=} opt_parent Optional parent node.
|
||||
* @constructor
|
||||
* @final
|
||||
* @template T
|
||||
*/
|
||||
goog.structs.AvlTree.Node = function(value, opt_parent) {
|
||||
/**
|
||||
* The value stored by the node.
|
||||
*
|
||||
* @type {T}
|
||||
*/
|
||||
this.value = value;
|
||||
|
||||
/**
|
||||
* The node's parent. Null if the node is the root.
|
||||
*
|
||||
* @type {goog.structs.AvlTree.Node<T>}
|
||||
*/
|
||||
this.parent = opt_parent ? opt_parent : null;
|
||||
|
||||
/**
|
||||
* The number of nodes in the subtree rooted at this node.
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
this.count = 1;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* The node's left child. Null if the node does not have a left child.
|
||||
*
|
||||
* @type {?goog.structs.AvlTree.Node<T>}
|
||||
*/
|
||||
goog.structs.AvlTree.Node.prototype.left = null;
|
||||
|
||||
|
||||
/**
|
||||
* The node's right child. Null if the node does not have a right child.
|
||||
*
|
||||
* @type {?goog.structs.AvlTree.Node<T>}
|
||||
*/
|
||||
goog.structs.AvlTree.Node.prototype.right = null;
|
||||
|
||||
|
||||
/**
|
||||
* The height of the tree rooted at this node.
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
goog.structs.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.
|
||||
*/
|
||||
goog.structs.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.
|
||||
*/
|
||||
goog.structs.AvlTree.Node.prototype.isLeftChild = function() {
|
||||
return !!this.parent && this.parent.left == this;
|
||||
};
|
||||
@@ -0,0 +1,22 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
Copyright 2007 The Closure Library Authors. All Rights Reserved.
|
||||
|
||||
Use of this source code is governed by the Apache License, Version 2.0.
|
||||
See the COPYING file for details.
|
||||
-->
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<title>
|
||||
Closure Unit Tests - goog.structs.AvlTree
|
||||
</title>
|
||||
<script src="../base.js">
|
||||
</script>
|
||||
<script>
|
||||
goog.require('goog.structs.AvlTreeTest');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,363 @@
|
||||
// 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.
|
||||
|
||||
goog.provide('goog.structs.AvlTreeTest');
|
||||
goog.setTestOnly('goog.structs.AvlTreeTest');
|
||||
|
||||
goog.require('goog.array');
|
||||
goog.require('goog.structs.AvlTree');
|
||||
goog.require('goog.testing.jsunit');
|
||||
|
||||
|
||||
/**
|
||||
* This test verifies that we can insert strings into the AvlTree and have
|
||||
* them be stored and sorted correctly by the default comparator.
|
||||
*/
|
||||
function testInsertsWithDefaultComparator() {
|
||||
var tree = new goog.structs.AvlTree();
|
||||
var values = ['bill', 'blake', 'elliot', 'jacob', 'john', 'myles', 'ted'];
|
||||
|
||||
// Insert strings into tree out of order
|
||||
tree.add(values[4]);
|
||||
tree.add(values[3]);
|
||||
tree.add(values[0]);
|
||||
tree.add(values[6]);
|
||||
tree.add(values[5]);
|
||||
tree.add(values[1]);
|
||||
tree.add(values[2]);
|
||||
|
||||
// Verify strings are stored in sorted order
|
||||
var i = 0;
|
||||
tree.inOrderTraverse(function(value) {
|
||||
assertEquals(values[i], value);
|
||||
i += 1;
|
||||
});
|
||||
assertEquals(i, values.length);
|
||||
|
||||
// Verify that no nodes are visited if the start value is larger than all
|
||||
// values
|
||||
tree.inOrderTraverse(function(value) {
|
||||
fail();
|
||||
}, 'zed');
|
||||
|
||||
// Verify strings are stored in sorted order
|
||||
i = values.length;
|
||||
tree.reverseOrderTraverse(function(value) {
|
||||
i--;
|
||||
assertEquals(values[i], value);
|
||||
});
|
||||
assertEquals(i, 0);
|
||||
|
||||
// Verify that no nodes are visited if the start value is smaller than all
|
||||
// values
|
||||
tree.reverseOrderTraverse(function(value) {
|
||||
fail();
|
||||
}, 'aardvark');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This test verifies that we can insert strings into and remove strings from
|
||||
* the AvlTree and have the only the non-removed values be stored and sorted
|
||||
* correctly by the default comparator.
|
||||
*/
|
||||
function testRemovesWithDefaultComparator() {
|
||||
var tree = new goog.structs.AvlTree();
|
||||
var values = ['bill', 'blake', 'elliot', 'jacob', 'john', 'myles', 'ted'];
|
||||
|
||||
// Insert strings into tree out of order
|
||||
tree.add('frodo');
|
||||
tree.add(values[4]);
|
||||
tree.add(values[3]);
|
||||
tree.add(values[0]);
|
||||
tree.add(values[6]);
|
||||
tree.add('samwise');
|
||||
tree.add(values[5]);
|
||||
tree.add(values[1]);
|
||||
tree.add(values[2]);
|
||||
tree.add('pippin');
|
||||
|
||||
// Remove strings from tree
|
||||
assertEquals(tree.remove('samwise'), 'samwise');
|
||||
assertEquals(tree.remove('pippin'), 'pippin');
|
||||
assertEquals(tree.remove('frodo'), 'frodo');
|
||||
assertEquals(tree.remove('merry'), null);
|
||||
|
||||
|
||||
// Verify strings are stored in sorted order
|
||||
var i = 0;
|
||||
tree.inOrderTraverse(function(value) {
|
||||
assertEquals(values[i], value);
|
||||
i += 1;
|
||||
});
|
||||
assertEquals(i, values.length);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This test verifies that we can insert values into and remove values from
|
||||
* the AvlTree and have them be stored and sorted correctly by a custom
|
||||
* comparator.
|
||||
*/
|
||||
function testInsertsAndRemovesWithCustomComparator() {
|
||||
var tree = new goog.structs.AvlTree(function(a, b) {
|
||||
return a - b;
|
||||
});
|
||||
|
||||
var NUM_TO_INSERT = 37;
|
||||
var valuesToRemove = [1, 0, 6, 7, 36];
|
||||
|
||||
// Insert ints into tree out of order
|
||||
var values = [];
|
||||
for (var i = 0; i < NUM_TO_INSERT; i += 1) {
|
||||
tree.add(i);
|
||||
values.push(i);
|
||||
}
|
||||
|
||||
for (var i = 0; i < valuesToRemove.length; i += 1) {
|
||||
assertEquals(tree.remove(valuesToRemove[i]), valuesToRemove[i]);
|
||||
goog.array.remove(values, valuesToRemove[i]);
|
||||
}
|
||||
assertEquals(tree.remove(-1), null);
|
||||
assertEquals(tree.remove(37), null);
|
||||
|
||||
// Verify strings are stored in sorted order
|
||||
var i = 0;
|
||||
tree.inOrderTraverse(function(value) {
|
||||
assertEquals(values[i], value);
|
||||
i += 1;
|
||||
});
|
||||
assertEquals(i, values.length);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This test verifies that we can insert values into and remove values from
|
||||
* the AvlTree and have it maintain the AVL-Tree upperbound on its height.
|
||||
*/
|
||||
function testAvlTreeHeight() {
|
||||
var tree = new goog.structs.AvlTree(function(a, b) {
|
||||
return a - b;
|
||||
});
|
||||
|
||||
var NUM_TO_INSERT = 2000;
|
||||
var NUM_TO_REMOVE = 500;
|
||||
|
||||
// Insert ints into tree out of order
|
||||
for (var i = 0; i < NUM_TO_INSERT; i += 1) {
|
||||
tree.add(i);
|
||||
}
|
||||
|
||||
// Remove valuse
|
||||
for (var i = 0; i < NUM_TO_REMOVE; i += 1) {
|
||||
tree.remove(i);
|
||||
}
|
||||
|
||||
assertTrue(tree.getHeight() <= 1.4405 *
|
||||
(Math.log(NUM_TO_INSERT - NUM_TO_REMOVE + 2) / Math.log(2)) - 1.3277);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This test verifies that we can insert values into and remove values from
|
||||
* the AvlTree and have its contains method correctly determine the values it
|
||||
* is contains.
|
||||
*/
|
||||
function testAvlTreeContains() {
|
||||
var tree = new goog.structs.AvlTree();
|
||||
var values = ['bill', 'blake', 'elliot', 'jacob', 'john', 'myles', 'ted'];
|
||||
|
||||
// Insert strings into tree out of order
|
||||
tree.add('frodo');
|
||||
tree.add(values[4]);
|
||||
tree.add(values[3]);
|
||||
tree.add(values[0]);
|
||||
tree.add(values[6]);
|
||||
tree.add('samwise');
|
||||
tree.add(values[5]);
|
||||
tree.add(values[1]);
|
||||
tree.add(values[2]);
|
||||
tree.add('pippin');
|
||||
|
||||
// Remove strings from tree
|
||||
assertEquals(tree.remove('samwise'), 'samwise');
|
||||
assertEquals(tree.remove('pippin'), 'pippin');
|
||||
assertEquals(tree.remove('frodo'), 'frodo');
|
||||
|
||||
for (var i = 0; i < values.length; i += 1) {
|
||||
assertTrue(tree.contains(values[i]));
|
||||
}
|
||||
assertFalse(tree.contains('samwise'));
|
||||
assertFalse(tree.contains('pippin'));
|
||||
assertFalse(tree.contains('frodo'));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This test verifies that we can insert values into and remove values from
|
||||
* the AvlTree and have its indexOf method correctly determine the in-order
|
||||
* index of the values it contains.
|
||||
*/
|
||||
function testAvlTreeIndexOf() {
|
||||
var tree = new goog.structs.AvlTree();
|
||||
var values = ['bill', 'blake', 'elliot', 'jacob', 'john', 'myles', 'ted'];
|
||||
|
||||
// Insert strings into tree out of order
|
||||
tree.add('frodo');
|
||||
tree.add(values[4]);
|
||||
tree.add(values[3]);
|
||||
tree.add(values[0]);
|
||||
tree.add(values[6]);
|
||||
tree.add('samwise');
|
||||
tree.add(values[5]);
|
||||
tree.add(values[1]);
|
||||
tree.add(values[2]);
|
||||
tree.add('pippin');
|
||||
|
||||
// Remove strings from tree
|
||||
assertEquals('samwise', tree.remove('samwise'));
|
||||
assertEquals('pippin', tree.remove('pippin'));
|
||||
assertEquals('frodo', tree.remove('frodo'));
|
||||
|
||||
for (var i = 0; i < values.length; i += 1) {
|
||||
assertEquals(i, tree.indexOf(values[i]));
|
||||
}
|
||||
assertEquals(-1, tree.indexOf('samwise'));
|
||||
assertEquals(-1, tree.indexOf('pippin'));
|
||||
assertEquals(-1, tree.indexOf('frodo'));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This test verifies that we can insert values into and remove values from
|
||||
* the AvlTree and have its minValue and maxValue routines return the correct
|
||||
* min and max values contained by the tree.
|
||||
*/
|
||||
function testMinAndMaxValues() {
|
||||
var tree = new goog.structs.AvlTree(function(a, b) {
|
||||
return a - b;
|
||||
});
|
||||
|
||||
var NUM_TO_INSERT = 2000;
|
||||
var NUM_TO_REMOVE = 500;
|
||||
|
||||
// Insert ints into tree out of order
|
||||
for (var i = 0; i < NUM_TO_INSERT; i += 1) {
|
||||
tree.add(i);
|
||||
}
|
||||
|
||||
// Remove valuse
|
||||
for (var i = 0; i < NUM_TO_REMOVE; i += 1) {
|
||||
tree.remove(i);
|
||||
}
|
||||
|
||||
assertEquals(tree.getMinimum(), NUM_TO_REMOVE);
|
||||
assertEquals(tree.getMaximum(), NUM_TO_INSERT - 1);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This test verifies that we can insert values into and remove values from
|
||||
* the AvlTree and traverse the tree in reverse order using the
|
||||
* reverseOrderTraverse routine.
|
||||
*/
|
||||
function testReverseOrderTraverse() {
|
||||
var tree = new goog.structs.AvlTree(function(a, b) {
|
||||
return a - b;
|
||||
});
|
||||
|
||||
var NUM_TO_INSERT = 2000;
|
||||
var NUM_TO_REMOVE = 500;
|
||||
|
||||
// Insert ints into tree out of order
|
||||
for (var i = 0; i < NUM_TO_INSERT; i += 1) {
|
||||
tree.add(i);
|
||||
}
|
||||
|
||||
// Remove valuse
|
||||
for (var i = 0; i < NUM_TO_REMOVE; i += 1) {
|
||||
tree.remove(i);
|
||||
}
|
||||
|
||||
var i = NUM_TO_INSERT - 1;
|
||||
tree.reverseOrderTraverse(function(value) {
|
||||
assertEquals(value, i);
|
||||
i -= 1;
|
||||
});
|
||||
assertEquals(i, NUM_TO_REMOVE - 1);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Verifies correct behavior of getCount(). See http://b/4347755
|
||||
*/
|
||||
function testGetCountBehavior() {
|
||||
var tree = new goog.structs.AvlTree();
|
||||
tree.add(1);
|
||||
tree.remove(1);
|
||||
assertEquals(0, tree.getCount());
|
||||
|
||||
var values = ['bill', 'blake', 'elliot', 'jacob', 'john', 'myles', 'ted'];
|
||||
|
||||
// Insert strings into tree out of order
|
||||
tree.add('frodo');
|
||||
tree.add(values[4]);
|
||||
tree.add(values[3]);
|
||||
tree.add(values[0]);
|
||||
tree.add(values[6]);
|
||||
tree.add('samwise');
|
||||
tree.add(values[5]);
|
||||
tree.add(values[1]);
|
||||
tree.add(values[2]);
|
||||
tree.add('pippin');
|
||||
assertEquals(10, tree.getCount());
|
||||
assertEquals(
|
||||
tree.root_.left.count + tree.root_.right.count + 1, tree.getCount());
|
||||
|
||||
// Remove strings from tree
|
||||
assertEquals('samwise', tree.remove('samwise'));
|
||||
assertEquals('pippin', tree.remove('pippin'));
|
||||
assertEquals('frodo', tree.remove('frodo'));
|
||||
assertEquals(null, tree.remove('merry'));
|
||||
assertEquals(7, tree.getCount());
|
||||
|
||||
assertEquals(
|
||||
tree.root_.left.count + tree.root_.right.count + 1, tree.getCount());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This test verifies that getKthOrder gets the correct value.
|
||||
*/
|
||||
function testGetKthOrder() {
|
||||
var tree = new goog.structs.AvlTree(function(a, b) {
|
||||
return a - b;
|
||||
});
|
||||
|
||||
var NUM_TO_INSERT = 2000;
|
||||
var NUM_TO_REMOVE = 500;
|
||||
|
||||
// Insert ints into tree out of order
|
||||
for (var i = 0; i < NUM_TO_INSERT; i += 1) {
|
||||
tree.add(i);
|
||||
}
|
||||
|
||||
// Remove values.
|
||||
for (var i = 0; i < NUM_TO_REMOVE; i += 1) {
|
||||
tree.remove(i);
|
||||
}
|
||||
for (var k = 0; k < tree.getCount(); ++k) {
|
||||
assertEquals(NUM_TO_REMOVE + k, tree.getKthValue(k));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
// Copyright 2006 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: Circular Buffer.
|
||||
*
|
||||
* Implements a buffer with a maximum size. New entries override the oldest
|
||||
* entries when the maximum size has been reached.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
goog.provide('goog.structs.CircularBuffer');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Class for CircularBuffer.
|
||||
* @param {number=} opt_maxSize The maximum size of the buffer.
|
||||
* @constructor
|
||||
* @template T
|
||||
*/
|
||||
goog.structs.CircularBuffer = function(opt_maxSize) {
|
||||
/**
|
||||
* Index of the next element in the circular array structure.
|
||||
* @private {number}
|
||||
*/
|
||||
this.nextPtr_ = 0;
|
||||
|
||||
/**
|
||||
* Maximum size of the the circular array structure.
|
||||
* @private {number}
|
||||
*/
|
||||
this.maxSize_ = opt_maxSize || 100;
|
||||
|
||||
/**
|
||||
* Underlying array for the CircularBuffer.
|
||||
* @private {!Array<T>}
|
||||
*/
|
||||
this.buff_ = [];
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Adds an item to the buffer. May remove the oldest item if the buffer is at
|
||||
* max size.
|
||||
* @param {T} item The item to add.
|
||||
* @return {T|undefined} The removed old item, if the buffer is at max size.
|
||||
* Return undefined, otherwise.
|
||||
*/
|
||||
goog.structs.CircularBuffer.prototype.add = function(item) {
|
||||
var previousItem = this.buff_[this.nextPtr_];
|
||||
this.buff_[this.nextPtr_] = item;
|
||||
this.nextPtr_ = (this.nextPtr_ + 1) % this.maxSize_;
|
||||
return previousItem;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the item at the specified index.
|
||||
* @param {number} index The index of the item. The index of an item can change
|
||||
* after calls to {@code add()} if the buffer is at maximum size.
|
||||
* @return {T} The item at the specified index.
|
||||
*/
|
||||
goog.structs.CircularBuffer.prototype.get = function(index) {
|
||||
index = this.normalizeIndex_(index);
|
||||
return this.buff_[index];
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sets the item at the specified index.
|
||||
* @param {number} index The index of the item. The index of an item can change
|
||||
* after calls to {@code add()} if the buffer is at maximum size.
|
||||
* @param {T} item The item to add.
|
||||
*/
|
||||
goog.structs.CircularBuffer.prototype.set = function(index, item) {
|
||||
index = this.normalizeIndex_(index);
|
||||
this.buff_[index] = item;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the current number of items in the buffer.
|
||||
* @return {number} The current number of items in the buffer.
|
||||
*/
|
||||
goog.structs.CircularBuffer.prototype.getCount = function() {
|
||||
return this.buff_.length;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {boolean} Whether the buffer is empty.
|
||||
*/
|
||||
goog.structs.CircularBuffer.prototype.isEmpty = function() {
|
||||
return this.buff_.length == 0;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Empties the current buffer.
|
||||
*/
|
||||
goog.structs.CircularBuffer.prototype.clear = function() {
|
||||
this.buff_.length = 0;
|
||||
this.nextPtr_ = 0;
|
||||
};
|
||||
|
||||
|
||||
/** @return {!Array<T>} The values in the buffer. */
|
||||
goog.structs.CircularBuffer.prototype.getValues = function() {
|
||||
// getNewestValues returns all the values if the maxCount parameter is the
|
||||
// count
|
||||
return this.getNewestValues(this.getCount());
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the newest values in the buffer up to {@code count}.
|
||||
* @param {number} maxCount The maximum number of values to get. Should be a
|
||||
* positive number.
|
||||
* @return {!Array<T>} The newest values in the buffer up to {@code count}.
|
||||
*/
|
||||
goog.structs.CircularBuffer.prototype.getNewestValues = function(maxCount) {
|
||||
var l = this.getCount();
|
||||
var start = this.getCount() - maxCount;
|
||||
var rv = [];
|
||||
for (var i = start; i < l; i++) {
|
||||
rv.push(this.get(i));
|
||||
}
|
||||
return rv;
|
||||
};
|
||||
|
||||
|
||||
/** @return {!Array<number>} The indexes in the buffer. */
|
||||
goog.structs.CircularBuffer.prototype.getKeys = function() {
|
||||
var rv = [];
|
||||
var l = this.getCount();
|
||||
for (var i = 0; i < l; i++) {
|
||||
rv[i] = i;
|
||||
}
|
||||
return rv;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Whether the buffer contains the key/index.
|
||||
* @param {number} key The key/index to check for.
|
||||
* @return {boolean} Whether the buffer contains the key/index.
|
||||
*/
|
||||
goog.structs.CircularBuffer.prototype.containsKey = function(key) {
|
||||
return key < this.getCount();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Whether the buffer contains the given value.
|
||||
* @param {T} value The value to check for.
|
||||
* @return {boolean} Whether the buffer contains the given value.
|
||||
*/
|
||||
goog.structs.CircularBuffer.prototype.containsValue = function(value) {
|
||||
var l = this.getCount();
|
||||
for (var i = 0; i < l; i++) {
|
||||
if (this.get(i) == value) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the last item inserted into the buffer.
|
||||
* @return {T|null} The last item inserted into the buffer,
|
||||
* or null if the buffer is empty.
|
||||
*/
|
||||
goog.structs.CircularBuffer.prototype.getLast = function() {
|
||||
if (this.getCount() == 0) {
|
||||
return null;
|
||||
}
|
||||
return this.get(this.getCount() - 1);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Helper function to convert an index in the number space of oldest to
|
||||
* newest items in the array to the position that the element will be at in the
|
||||
* underlying array.
|
||||
* @param {number} index The index of the item in a list ordered from oldest to
|
||||
* newest.
|
||||
* @return {number} The index of the item in the CircularBuffer's underlying
|
||||
* array.
|
||||
* @private
|
||||
*/
|
||||
goog.structs.CircularBuffer.prototype.normalizeIndex_ = function(index) {
|
||||
if (index >= this.buff_.length) {
|
||||
throw Error('Out of bounds exception');
|
||||
}
|
||||
|
||||
if (this.buff_.length < this.maxSize_) {
|
||||
return index;
|
||||
}
|
||||
|
||||
return (this.nextPtr_ + Number(index)) % this.maxSize_;
|
||||
};
|
||||
@@ -0,0 +1,22 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
Copyright 2006 The Closure Library Authors. All Rights Reserved.
|
||||
|
||||
Use of this source code is governed by the Apache License, Version 2.0.
|
||||
See the COPYING file for details.
|
||||
-->
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<title>
|
||||
Closure Unit Tests - goog.structs
|
||||
</title>
|
||||
<script src="../base.js">
|
||||
</script>
|
||||
<script>
|
||||
goog.require('goog.structs.CircularBufferTest');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,101 @@
|
||||
// Copyright 2006 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.
|
||||
|
||||
goog.provide('goog.structs.CircularBufferTest');
|
||||
goog.setTestOnly('goog.structs.CircularBufferTest');
|
||||
|
||||
goog.require('goog.structs.CircularBuffer');
|
||||
goog.require('goog.testing.jsunit');
|
||||
|
||||
function testCircularBuffer() {
|
||||
var buff = new goog.structs.CircularBuffer(2);
|
||||
assertUndefined(buff.add('first'));
|
||||
assertEquals(1, buff.getCount());
|
||||
assertEquals('first', buff.get(0));
|
||||
assertEquals('first', buff.getLast());
|
||||
assertUndefined(buff.add('second'));
|
||||
assertEquals(2, buff.getCount());
|
||||
assertEquals('first', buff.get(0));
|
||||
assertEquals('second', buff.get(1));
|
||||
assertEquals('second', buff.getLast());
|
||||
assertEquals('first', buff.add('third'));
|
||||
assertEquals(2, buff.getCount());
|
||||
assertEquals('second', buff.get(0));
|
||||
assertEquals('third', buff.get(1));
|
||||
assertEquals('third', buff.getLast());
|
||||
}
|
||||
|
||||
function testIsEmpty() {
|
||||
var buff = new goog.structs.CircularBuffer(2);
|
||||
assertTrue('initially empty', buff.isEmpty());
|
||||
assertUndefined(buff.add('first'));
|
||||
assertFalse('not empty after add empty', buff.isEmpty());
|
||||
}
|
||||
|
||||
function testClear() {
|
||||
var buff = new goog.structs.CircularBuffer(2);
|
||||
assertUndefined(buff.add('first'));
|
||||
buff.clear();
|
||||
assertTrue('should be empty after clear', buff.isEmpty());
|
||||
|
||||
}
|
||||
|
||||
function testGetValues() {
|
||||
var buff = new goog.structs.CircularBuffer(2);
|
||||
assertUndefined(buff.add('first'));
|
||||
assertUndefined(buff.add('second'));
|
||||
assertArrayEquals(['first', 'second'], buff.getValues());
|
||||
}
|
||||
|
||||
function testGetNewestValues() {
|
||||
var buff = new goog.structs.CircularBuffer(5);
|
||||
assertUndefined(buff.add('first'));
|
||||
assertUndefined(buff.add('second'));
|
||||
assertUndefined(buff.add('third'));
|
||||
assertUndefined(buff.add('fourth'));
|
||||
assertUndefined(buff.add('fifth'));
|
||||
assertArrayEquals(['fourth', 'fifth'], buff.getNewestValues(2));
|
||||
}
|
||||
|
||||
|
||||
function testGetKeys() {
|
||||
var buff = new goog.structs.CircularBuffer(2);
|
||||
assertUndefined(buff.add('first'));
|
||||
assertUndefined(buff.add('second'));
|
||||
assertArrayEquals([0, 1], buff.getKeys());
|
||||
}
|
||||
|
||||
function testContainsValue() {
|
||||
var buff = new goog.structs.CircularBuffer(2);
|
||||
assertUndefined(buff.add('first'));
|
||||
assertUndefined(buff.add('second'));
|
||||
assertTrue(buff.containsValue('first'));
|
||||
assertTrue(buff.containsValue('second'));
|
||||
assertFalse(buff.containsValue('third'));
|
||||
}
|
||||
|
||||
function testContainsKey() {
|
||||
var buff = new goog.structs.CircularBuffer(3);
|
||||
assertUndefined(buff.add('first'));
|
||||
assertUndefined(buff.add('second'));
|
||||
assertUndefined(buff.add('third'));
|
||||
assertTrue(buff.containsKey(0));
|
||||
assertTrue(buff.containsKey('0'));
|
||||
assertTrue(buff.containsKey(1));
|
||||
assertTrue(buff.containsKey('1'));
|
||||
assertTrue(buff.containsKey(2));
|
||||
assertTrue(buff.containsKey('2'));
|
||||
assertFalse(buff.containsKey(3));
|
||||
assertFalse(buff.containsKey('3'));
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
// Copyright 2011 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 Defines the collection interface.
|
||||
*
|
||||
* @author nnaze@google.com (Nathan Naze)
|
||||
*/
|
||||
|
||||
goog.provide('goog.structs.Collection');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* An interface for a collection of values.
|
||||
* @interface
|
||||
* @template T
|
||||
*/
|
||||
goog.structs.Collection = function() {};
|
||||
|
||||
|
||||
/**
|
||||
* @param {T} value Value to add to the collection.
|
||||
*/
|
||||
goog.structs.Collection.prototype.add;
|
||||
|
||||
|
||||
/**
|
||||
* @param {T} value Value to remove from the collection.
|
||||
*/
|
||||
goog.structs.Collection.prototype.remove;
|
||||
|
||||
|
||||
/**
|
||||
* @param {T} value Value to find in the collection.
|
||||
* @return {boolean} Whether the collection contains the specified value.
|
||||
*/
|
||||
goog.structs.Collection.prototype.contains;
|
||||
|
||||
|
||||
/**
|
||||
* @return {number} The number of values stored in the collection.
|
||||
*/
|
||||
goog.structs.Collection.prototype.getCount;
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<!--
|
||||
Copyright 2011 The Closure Library Authors. All Rights Reserved.
|
||||
|
||||
Use of this source code is governed by the Apache License, Version 2.0.
|
||||
See the COPYING file for details.
|
||||
-->
|
||||
<head>
|
||||
<title>
|
||||
Closure Unit Tests - goog.structs.Collection
|
||||
</title>
|
||||
<script src="../base.js">
|
||||
</script>
|
||||
<script>
|
||||
goog.require('goog.structs.CollectionTest');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,53 @@
|
||||
// Copyright 2011 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.
|
||||
|
||||
goog.provide('goog.structs.CollectionTest');
|
||||
goog.setTestOnly('goog.structs.CollectionTest');
|
||||
|
||||
goog.require('goog.structs.AvlTree');
|
||||
goog.require('goog.structs.Set');
|
||||
goog.require('goog.testing.jsunit');
|
||||
|
||||
function testSet() {
|
||||
var set = new goog.structs.Set();
|
||||
exerciseCollection(set);
|
||||
}
|
||||
|
||||
function testAvlTree() {
|
||||
var tree = new goog.structs.AvlTree();
|
||||
exerciseCollection(tree);
|
||||
}
|
||||
|
||||
// Simple exercise of a collection object.
|
||||
function exerciseCollection(collection) {
|
||||
assertEquals(0, collection.getCount());
|
||||
|
||||
for (var i = 1; i <= 10; i++) {
|
||||
assertFalse(collection.contains(i));
|
||||
collection.add(i);
|
||||
assertTrue(collection.contains(i));
|
||||
assertEquals(i, collection.getCount());
|
||||
}
|
||||
|
||||
assertEquals(10, collection.getCount());
|
||||
|
||||
for (var i = 10; i > 0; i--) {
|
||||
assertTrue(collection.contains(i));
|
||||
collection.remove(i);
|
||||
assertFalse(collection.contains(i));
|
||||
assertEquals(i - 1, collection.getCount());
|
||||
}
|
||||
|
||||
assertEquals(0, collection.getCount());
|
||||
}
|
||||
@@ -0,0 +1,334 @@
|
||||
// Copyright 2006 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: Heap.
|
||||
*
|
||||
*
|
||||
* This file provides the implementation of a Heap datastructure. Smaller keys
|
||||
* rise to the top.
|
||||
*
|
||||
* The big-O notation for all operations are below:
|
||||
* <pre>
|
||||
* Method big-O
|
||||
* ----------------------------------------------------------------------------
|
||||
* - insert O(logn)
|
||||
* - remove O(logn)
|
||||
* - peek O(1)
|
||||
* - contains O(n)
|
||||
* </pre>
|
||||
*/
|
||||
// TODO(user): Should this rely on natural ordering via some Comparable
|
||||
// interface?
|
||||
|
||||
|
||||
goog.provide('goog.structs.Heap');
|
||||
|
||||
goog.require('goog.array');
|
||||
goog.require('goog.object');
|
||||
goog.require('goog.structs.Node');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Class for a Heap datastructure.
|
||||
*
|
||||
* @param {goog.structs.Heap|Object=} opt_heap Optional goog.structs.Heap or
|
||||
* Object to initialize heap with.
|
||||
* @constructor
|
||||
* @template K, V
|
||||
*/
|
||||
goog.structs.Heap = function(opt_heap) {
|
||||
/**
|
||||
* The nodes of the heap.
|
||||
* @private
|
||||
* @type {Array<goog.structs.Node>}
|
||||
*/
|
||||
this.nodes_ = [];
|
||||
|
||||
if (opt_heap) {
|
||||
this.insertAll(opt_heap);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Insert the given value into the heap with the given key.
|
||||
* @param {K} key The key.
|
||||
* @param {V} value The value.
|
||||
*/
|
||||
goog.structs.Heap.prototype.insert = function(key, value) {
|
||||
var node = new goog.structs.Node(key, value);
|
||||
var nodes = this.nodes_;
|
||||
nodes.push(node);
|
||||
this.moveUp_(nodes.length - 1);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Adds multiple key-value pairs from another goog.structs.Heap or Object
|
||||
* @param {goog.structs.Heap|Object} heap Object containing the data to add.
|
||||
*/
|
||||
goog.structs.Heap.prototype.insertAll = function(heap) {
|
||||
var keys, values;
|
||||
if (heap instanceof goog.structs.Heap) {
|
||||
keys = heap.getKeys();
|
||||
values = heap.getValues();
|
||||
|
||||
// If it is a heap and the current heap is empty, I can rely on the fact
|
||||
// that the keys/values are in the correct order to put in the underlying
|
||||
// structure.
|
||||
if (heap.getCount() <= 0) {
|
||||
var nodes = this.nodes_;
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
nodes.push(new goog.structs.Node(keys[i], values[i]));
|
||||
}
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
keys = goog.object.getKeys(heap);
|
||||
values = goog.object.getValues(heap);
|
||||
}
|
||||
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
this.insert(keys[i], values[i]);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Retrieves and removes the root value of this heap.
|
||||
* @return {V} The value removed from the root of the heap. Returns
|
||||
* undefined if the heap is empty.
|
||||
*/
|
||||
goog.structs.Heap.prototype.remove = function() {
|
||||
var nodes = this.nodes_;
|
||||
var count = nodes.length;
|
||||
var rootNode = nodes[0];
|
||||
if (count <= 0) {
|
||||
return undefined;
|
||||
} else if (count == 1) {
|
||||
goog.array.clear(nodes);
|
||||
} else {
|
||||
nodes[0] = nodes.pop();
|
||||
this.moveDown_(0);
|
||||
}
|
||||
return rootNode.getValue();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Retrieves but does not remove the root value of this heap.
|
||||
* @return {V} The value at the root of the heap. Returns
|
||||
* undefined if the heap is empty.
|
||||
*/
|
||||
goog.structs.Heap.prototype.peek = function() {
|
||||
var nodes = this.nodes_;
|
||||
if (nodes.length == 0) {
|
||||
return undefined;
|
||||
}
|
||||
return nodes[0].getValue();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Retrieves but does not remove the key of the root node of this heap.
|
||||
* @return {K} The key at the root of the heap. Returns undefined if the
|
||||
* heap is empty.
|
||||
*/
|
||||
goog.structs.Heap.prototype.peekKey = function() {
|
||||
return this.nodes_[0] && this.nodes_[0].getKey();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Moves the node at the given index down to its proper place in the heap.
|
||||
* @param {number} index The index of the node to move down.
|
||||
* @private
|
||||
*/
|
||||
goog.structs.Heap.prototype.moveDown_ = function(index) {
|
||||
var nodes = this.nodes_;
|
||||
var count = nodes.length;
|
||||
|
||||
// Save the node being moved down.
|
||||
var node = nodes[index];
|
||||
// While the current node has a child.
|
||||
while (index < (count >> 1)) {
|
||||
var leftChildIndex = this.getLeftChildIndex_(index);
|
||||
var rightChildIndex = this.getRightChildIndex_(index);
|
||||
|
||||
// Determine the index of the smaller child.
|
||||
var smallerChildIndex = rightChildIndex < count &&
|
||||
nodes[rightChildIndex].getKey() < nodes[leftChildIndex].getKey() ?
|
||||
rightChildIndex : leftChildIndex;
|
||||
|
||||
// If the node being moved down is smaller than its children, the node
|
||||
// has found the correct index it should be at.
|
||||
if (nodes[smallerChildIndex].getKey() > node.getKey()) {
|
||||
break;
|
||||
}
|
||||
|
||||
// If not, then take the smaller child as the current node.
|
||||
nodes[index] = nodes[smallerChildIndex];
|
||||
index = smallerChildIndex;
|
||||
}
|
||||
nodes[index] = node;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Moves the node at the given index up to its proper place in the heap.
|
||||
* @param {number} index The index of the node to move up.
|
||||
* @private
|
||||
*/
|
||||
goog.structs.Heap.prototype.moveUp_ = function(index) {
|
||||
var nodes = this.nodes_;
|
||||
var node = nodes[index];
|
||||
|
||||
// While the node being moved up is not at the root.
|
||||
while (index > 0) {
|
||||
// If the parent is less than the node being moved up, move the parent down.
|
||||
var parentIndex = this.getParentIndex_(index);
|
||||
if (nodes[parentIndex].getKey() > node.getKey()) {
|
||||
nodes[index] = nodes[parentIndex];
|
||||
index = parentIndex;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
nodes[index] = node;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the index of the left child of the node at the given index.
|
||||
* @param {number} index The index of the node to get the left child for.
|
||||
* @return {number} The index of the left child.
|
||||
* @private
|
||||
*/
|
||||
goog.structs.Heap.prototype.getLeftChildIndex_ = function(index) {
|
||||
return index * 2 + 1;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the index of the right child of the node at the given index.
|
||||
* @param {number} index The index of the node to get the right child for.
|
||||
* @return {number} The index of the right child.
|
||||
* @private
|
||||
*/
|
||||
goog.structs.Heap.prototype.getRightChildIndex_ = function(index) {
|
||||
return index * 2 + 2;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the index of the parent of the node at the given index.
|
||||
* @param {number} index The index of the node to get the parent for.
|
||||
* @return {number} The index of the parent.
|
||||
* @private
|
||||
*/
|
||||
goog.structs.Heap.prototype.getParentIndex_ = function(index) {
|
||||
return (index - 1) >> 1;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the values of the heap.
|
||||
* @return {!Array<V>} The values in the heap.
|
||||
*/
|
||||
goog.structs.Heap.prototype.getValues = function() {
|
||||
var nodes = this.nodes_;
|
||||
var rv = [];
|
||||
var l = nodes.length;
|
||||
for (var i = 0; i < l; i++) {
|
||||
rv.push(nodes[i].getValue());
|
||||
}
|
||||
return rv;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the keys of the heap.
|
||||
* @return {!Array<K>} The keys in the heap.
|
||||
*/
|
||||
goog.structs.Heap.prototype.getKeys = function() {
|
||||
var nodes = this.nodes_;
|
||||
var rv = [];
|
||||
var l = nodes.length;
|
||||
for (var i = 0; i < l; i++) {
|
||||
rv.push(nodes[i].getKey());
|
||||
}
|
||||
return rv;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Whether the heap contains the given value.
|
||||
* @param {V} val The value to check for.
|
||||
* @return {boolean} Whether the heap contains the value.
|
||||
*/
|
||||
goog.structs.Heap.prototype.containsValue = function(val) {
|
||||
return goog.array.some(this.nodes_, function(node) {
|
||||
return node.getValue() == val;
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Whether the heap contains the given key.
|
||||
* @param {K} key The key to check for.
|
||||
* @return {boolean} Whether the heap contains the key.
|
||||
*/
|
||||
goog.structs.Heap.prototype.containsKey = function(key) {
|
||||
return goog.array.some(this.nodes_, function(node) {
|
||||
return node.getKey() == key;
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Clones a heap and returns a new heap
|
||||
* @return {!goog.structs.Heap} A new goog.structs.Heap with the same key-value
|
||||
* pairs.
|
||||
*/
|
||||
goog.structs.Heap.prototype.clone = function() {
|
||||
return new goog.structs.Heap(this);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* The number of key-value pairs in the map
|
||||
* @return {number} The number of pairs.
|
||||
*/
|
||||
goog.structs.Heap.prototype.getCount = function() {
|
||||
return this.nodes_.length;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns true if this heap contains no elements.
|
||||
* @return {boolean} Whether this heap contains no elements.
|
||||
*/
|
||||
goog.structs.Heap.prototype.isEmpty = function() {
|
||||
return goog.array.isEmpty(this.nodes_);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Removes all elements from the heap.
|
||||
*/
|
||||
goog.structs.Heap.prototype.clear = function() {
|
||||
goog.array.clear(this.nodes_);
|
||||
};
|
||||
@@ -0,0 +1,22 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
Copyright 2006 The Closure Library Authors. All Rights Reserved.
|
||||
|
||||
Use of this source code is governed by the Apache License, Version 2.0.
|
||||
See the COPYING file for details.
|
||||
-->
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<title>
|
||||
Closure Unit Tests - goog.structs.Heap
|
||||
</title>
|
||||
<script src="../base.js">
|
||||
</script>
|
||||
<script>
|
||||
goog.require('goog.structs.HeapTest');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,215 @@
|
||||
// Copyright 2006 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.
|
||||
|
||||
goog.provide('goog.structs.HeapTest');
|
||||
goog.setTestOnly('goog.structs.HeapTest');
|
||||
|
||||
goog.require('goog.structs');
|
||||
goog.require('goog.structs.Heap');
|
||||
goog.require('goog.testing.jsunit');
|
||||
|
||||
function getHeap() {
|
||||
var h = new goog.structs.Heap();
|
||||
h.insert(0, 'a');
|
||||
h.insert(1, 'b');
|
||||
h.insert(2, 'c');
|
||||
h.insert(3, 'd');
|
||||
return h;
|
||||
}
|
||||
|
||||
|
||||
function getHeap2() {
|
||||
var h = new goog.structs.Heap();
|
||||
h.insert(1, 'b');
|
||||
h.insert(3, 'd');
|
||||
h.insert(0, 'a');
|
||||
h.insert(2, 'c');
|
||||
return h;
|
||||
}
|
||||
|
||||
|
||||
function testGetCount1() {
|
||||
var h = getHeap();
|
||||
assertEquals('count, should be 4', h.getCount(), 4);
|
||||
h.remove();
|
||||
assertEquals('count, should be 3', h.getCount(), 3);
|
||||
}
|
||||
|
||||
function testGetCount2() {
|
||||
var h = getHeap();
|
||||
h.remove();
|
||||
h.remove();
|
||||
h.remove();
|
||||
h.remove();
|
||||
assertEquals('count, should be 0', h.getCount(), 0);
|
||||
}
|
||||
|
||||
|
||||
function testKeys() {
|
||||
var h = getHeap();
|
||||
var keys = h.getKeys();
|
||||
for (var i = 0; i < 4; i++) {
|
||||
assertTrue('getKeys, key ' + i + ' found', goog.structs.contains(keys, i));
|
||||
}
|
||||
assertEquals('getKeys, Should be 4 keys', goog.structs.getCount(keys), 4);
|
||||
}
|
||||
|
||||
|
||||
function testValues() {
|
||||
var h = getHeap();
|
||||
var values = h.getValues();
|
||||
|
||||
assertTrue('getKeys, value "a" found', goog.structs.contains(values, 'a'));
|
||||
assertTrue('getKeys, value "b" found', goog.structs.contains(values, 'b'));
|
||||
assertTrue('getKeys, value "c" found', goog.structs.contains(values, 'c'));
|
||||
assertTrue('getKeys, value "d" found', goog.structs.contains(values, 'd'));
|
||||
assertEquals('getKeys, Should be 4 keys', goog.structs.getCount(values), 4);
|
||||
}
|
||||
|
||||
|
||||
function testContainsKey() {
|
||||
var h = getHeap();
|
||||
|
||||
for (var i = 0; i < 4; i++) {
|
||||
assertTrue('containsKey, key ' + i + ' found', h.containsKey(i));
|
||||
}
|
||||
assertFalse('containsKey, value 4 not found', h.containsKey(4));
|
||||
}
|
||||
|
||||
|
||||
function testContainsValue() {
|
||||
var h = getHeap();
|
||||
|
||||
assertTrue('containsValue, value "a" found', h.containsValue('a'));
|
||||
assertTrue('containsValue, value "b" found', h.containsValue('b'));
|
||||
assertTrue('containsValue, value "c" found', h.containsValue('c'));
|
||||
assertTrue('containsValue, value "d" found', h.containsValue('d'));
|
||||
assertFalse('containsValue, value "e" not found', h.containsValue('e'));
|
||||
}
|
||||
|
||||
|
||||
function testClone() {
|
||||
var h = getHeap();
|
||||
var h2 = h.clone();
|
||||
assertTrue('clone so it should not be empty', !h2.isEmpty());
|
||||
assertTrue('clone so it should contain key 0', h2.containsKey(0));
|
||||
assertTrue('clone so it should contain value "a"', h2.containsValue('a'));
|
||||
}
|
||||
|
||||
|
||||
function testClear() {
|
||||
var h = getHeap();
|
||||
h.clear();
|
||||
assertTrue('cleared so it should be empty', h.isEmpty());
|
||||
}
|
||||
|
||||
|
||||
function testIsEmpty() {
|
||||
var h = getHeap();
|
||||
assertFalse('4 values so should not be empty', h.isEmpty());
|
||||
|
||||
h.remove();
|
||||
h.remove();
|
||||
h.remove();
|
||||
assertFalse('1 values so should not be empty', h.isEmpty());
|
||||
|
||||
h.remove();
|
||||
assertTrue('0 values so should be empty', h.isEmpty());
|
||||
}
|
||||
|
||||
|
||||
function testPeek1() {
|
||||
var h = getHeap();
|
||||
assertEquals('peek, Should be "a"', h.peek(), 'a');
|
||||
}
|
||||
|
||||
|
||||
function testPeek2() {
|
||||
var h = getHeap2();
|
||||
assertEquals('peek, Should be "a"', h.peek(), 'a');
|
||||
}
|
||||
|
||||
|
||||
function testPeek3() {
|
||||
var h = getHeap();
|
||||
h.clear();
|
||||
assertEquals('peek, Should be "undefined"', h.peek(), undefined);
|
||||
}
|
||||
|
||||
|
||||
function testPeekKey1() {
|
||||
var h = getHeap();
|
||||
assertEquals('peekKey, Should be "0"', h.peekKey(), 0);
|
||||
}
|
||||
|
||||
|
||||
function testPeekKey2() {
|
||||
var h = getHeap2();
|
||||
assertEquals('peekKey, Should be "0"', h.peekKey(), 0);
|
||||
}
|
||||
|
||||
|
||||
function testPeekKey3() {
|
||||
var h = getHeap();
|
||||
h.clear();
|
||||
assertEquals('peekKey, Should be "undefined"', h.peekKey(), undefined);
|
||||
}
|
||||
|
||||
|
||||
function testRemove1() {
|
||||
var h = getHeap();
|
||||
|
||||
assertEquals('remove, Should be "a"', h.remove(), 'a');
|
||||
assertEquals('remove, Should be "b"', h.remove(), 'b');
|
||||
assertEquals('remove, Should be "c"', h.remove(), 'c');
|
||||
assertEquals('remove, Should be "d"', h.remove(), 'd');
|
||||
}
|
||||
|
||||
|
||||
function testRemove2() {
|
||||
var h = getHeap2();
|
||||
|
||||
assertEquals('remove, Should be "a"', h.remove(), 'a');
|
||||
assertEquals('remove, Should be "b"', h.remove(), 'b');
|
||||
assertEquals('remove, Should be "c"', h.remove(), 'c');
|
||||
assertEquals('remove, Should be "d"', h.remove(), 'd');
|
||||
}
|
||||
|
||||
|
||||
function testInsertPeek1() {
|
||||
var h = new goog.structs.Heap();
|
||||
|
||||
h.insert(3, 'd');
|
||||
assertEquals('peek, Should be "d"', h.peek(), 'd');
|
||||
h.insert(2, 'c');
|
||||
assertEquals('peek, Should be "c"', h.peek(), 'c');
|
||||
h.insert(1, 'b');
|
||||
assertEquals('peek, Should be "b"', h.peek(), 'b');
|
||||
h.insert(0, 'a');
|
||||
assertEquals('peek, Should be "a"', h.peek(), 'a');
|
||||
}
|
||||
|
||||
|
||||
function testInsertPeek2() {
|
||||
var h = new goog.structs.Heap();
|
||||
|
||||
h.insert(1, 'b');
|
||||
assertEquals('peak, Should be "b"', h.peek(), 'b');
|
||||
h.insert(3, 'd');
|
||||
assertEquals('peak, Should be "b"', h.peek(), 'b');
|
||||
h.insert(0, 'a');
|
||||
assertEquals('peak, Should be "a"', h.peek(), 'a');
|
||||
h.insert(2, 'c');
|
||||
assertEquals('peak, Should be "a"', h.peek(), 'a');
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
// Copyright 2008 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 Provides inversion and inversion map functionality for storing
|
||||
* integer ranges and corresponding values.
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.structs.InversionMap');
|
||||
|
||||
goog.require('goog.array');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Maps ranges to values.
|
||||
* @param {Array<number>} rangeArray An array of monotonically
|
||||
* increasing integer values, with at least one instance.
|
||||
* @param {Array<T>} valueArray An array of corresponding values.
|
||||
* Length must be the same as rangeArray.
|
||||
* @param {boolean=} opt_delta If true, saves only delta from previous value.
|
||||
* @constructor
|
||||
* @template T
|
||||
*/
|
||||
goog.structs.InversionMap = function(rangeArray, valueArray, opt_delta) {
|
||||
/**
|
||||
* @protected {Array<number>}
|
||||
*/
|
||||
this.rangeArray = null;
|
||||
|
||||
if (rangeArray.length != valueArray.length) {
|
||||
// rangeArray and valueArray has to match in number of entries.
|
||||
return null;
|
||||
}
|
||||
this.storeInversion_(rangeArray, opt_delta);
|
||||
|
||||
/** @protected {Array<T>} */
|
||||
this.values = valueArray;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Stores the integers as ranges (half-open).
|
||||
* If delta is true, the integers are delta from the previous value and
|
||||
* will be restored to the absolute value.
|
||||
* When used as a set, even indices are IN, and odd are OUT.
|
||||
* @param {Array<number?>} rangeArray An array of monotonically
|
||||
* increasing integer values, with at least one instance.
|
||||
* @param {boolean=} opt_delta If true, saves only delta from previous value.
|
||||
* @private
|
||||
*/
|
||||
goog.structs.InversionMap.prototype.storeInversion_ = function(rangeArray,
|
||||
opt_delta) {
|
||||
this.rangeArray = rangeArray;
|
||||
|
||||
for (var i = 1; i < rangeArray.length; i++) {
|
||||
if (rangeArray[i] == null) {
|
||||
rangeArray[i] = rangeArray[i - 1] + 1;
|
||||
} else if (opt_delta) {
|
||||
rangeArray[i] += rangeArray[i - 1];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Splices a range -> value map into this inversion map.
|
||||
* @param {Array<number>} rangeArray An array of monotonically
|
||||
* increasing integer values, with at least one instance.
|
||||
* @param {Array<T>} valueArray An array of corresponding values.
|
||||
* Length must be the same as rangeArray.
|
||||
* @param {boolean=} opt_delta If true, saves only delta from previous value.
|
||||
*/
|
||||
goog.structs.InversionMap.prototype.spliceInversion = function(
|
||||
rangeArray, valueArray, opt_delta) {
|
||||
// By building another inversion map, we build the arrays that we need
|
||||
// to splice in.
|
||||
var otherMap = new goog.structs.InversionMap(
|
||||
rangeArray, valueArray, opt_delta);
|
||||
|
||||
// Figure out where to splice those arrays.
|
||||
var startRange = otherMap.rangeArray[0];
|
||||
var endRange =
|
||||
/** @type {number} */ (goog.array.peek(otherMap.rangeArray));
|
||||
var startSplice = this.getLeast(startRange);
|
||||
var endSplice = this.getLeast(endRange);
|
||||
|
||||
// The inversion map works by storing the start points of ranges...
|
||||
if (startRange != this.rangeArray[startSplice]) {
|
||||
// ...if we're splicing in a start point that isn't already here,
|
||||
// then we need to insert it after the insertion point.
|
||||
startSplice++;
|
||||
} // otherwise we overwrite the insertion point.
|
||||
|
||||
var spliceLength = endSplice - startSplice + 1;
|
||||
goog.partial(goog.array.splice, this.rangeArray, startSplice,
|
||||
spliceLength).apply(null, otherMap.rangeArray);
|
||||
goog.partial(goog.array.splice, this.values, startSplice,
|
||||
spliceLength).apply(null, otherMap.values);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the value corresponding to a number from the inversion map.
|
||||
* @param {number} intKey The number for which value needs to be retrieved
|
||||
* from inversion map.
|
||||
* @return {T|null} Value retrieved from inversion map; null if not found.
|
||||
*/
|
||||
goog.structs.InversionMap.prototype.at = function(intKey) {
|
||||
var index = this.getLeast(intKey);
|
||||
if (index < 0) {
|
||||
return null;
|
||||
}
|
||||
return this.values[index];
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the largest index such that rangeArray[index] <= intKey from the
|
||||
* inversion map.
|
||||
* @param {number} intKey The probe for which rangeArray is searched.
|
||||
* @return {number} Largest index such that rangeArray[index] <= intKey.
|
||||
* @protected
|
||||
*/
|
||||
goog.structs.InversionMap.prototype.getLeast = function(intKey) {
|
||||
var arr = this.rangeArray;
|
||||
var low = 0;
|
||||
var high = arr.length;
|
||||
while (high - low > 8) {
|
||||
var mid = (high + low) >> 1;
|
||||
if (arr[mid] <= intKey) {
|
||||
low = mid;
|
||||
} else {
|
||||
high = mid;
|
||||
}
|
||||
}
|
||||
for (; low < high; ++low) {
|
||||
if (intKey < arr[low]) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return low - 1;
|
||||
};
|
||||
@@ -0,0 +1,22 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
Copyright 2008 The Closure Library Authors. All Rights Reserved.
|
||||
|
||||
Use of this source code is governed by the Apache License, Version 2.0.
|
||||
See the COPYING file for details.
|
||||
-->
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<title>
|
||||
Closure Unit Tests - goog.structs.InversionMap
|
||||
</title>
|
||||
<script type="text/javascript" src="../base.js">
|
||||
</script>
|
||||
<script type="text/javascript">
|
||||
goog.require('goog.structs.InversionMapTest');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,154 @@
|
||||
// Copyright 2008 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.
|
||||
|
||||
goog.provide('goog.structs.InversionMapTest');
|
||||
goog.setTestOnly('goog.structs.InversionMapTest');
|
||||
|
||||
goog.require('goog.structs.InversionMap');
|
||||
goog.require('goog.testing.jsunit');
|
||||
|
||||
function testInversionWithDelta() {
|
||||
var alphabetNames = new goog.structs.InversionMap(
|
||||
[0, 97, 1, 1, 1, 20, 1, 1, 1],
|
||||
[null,
|
||||
'LATIN SMALL LETTER A',
|
||||
'LATIN SMALL LETTER B',
|
||||
'LATIN SMALL LETTER C',
|
||||
null,
|
||||
'LATIN SMALL LETTER X',
|
||||
'LATIN SMALL LETTER Y',
|
||||
'LATIN SMALL LETTER Z',
|
||||
null],
|
||||
true);
|
||||
|
||||
assertEquals('LATIN SMALL LETTER A', alphabetNames.at(97));
|
||||
assertEquals('LATIN SMALL LETTER Y', alphabetNames.at(121));
|
||||
assertEquals(null, alphabetNames.at(140));
|
||||
assertEquals(null, alphabetNames.at(0));
|
||||
}
|
||||
|
||||
function testInversionWithoutDelta() {
|
||||
var alphabetNames = new goog.structs.InversionMap(
|
||||
[0, 97, 98, 99, 100, 120, 121, 122, 123],
|
||||
[null,
|
||||
'LATIN SMALL LETTER A',
|
||||
'LATIN SMALL LETTER B',
|
||||
'LATIN SMALL LETTER C',
|
||||
null,
|
||||
'LATIN SMALL LETTER X',
|
||||
'LATIN SMALL LETTER Y',
|
||||
'LATIN SMALL LETTER Z',
|
||||
null],
|
||||
false);
|
||||
|
||||
assertEquals('LATIN SMALL LETTER A', alphabetNames.at(97));
|
||||
assertEquals('LATIN SMALL LETTER Y', alphabetNames.at(121));
|
||||
assertEquals(null, alphabetNames.at(140));
|
||||
assertEquals(null, alphabetNames.at(0));
|
||||
}
|
||||
|
||||
function testInversionWithoutDeltaNoOpt() {
|
||||
var alphabetNames = new goog.structs.InversionMap(
|
||||
[0, 97, 98, 99, 100, 120, 121, 122, 123],
|
||||
[null,
|
||||
'LATIN SMALL LETTER A',
|
||||
'LATIN SMALL LETTER B',
|
||||
'LATIN SMALL LETTER C',
|
||||
null,
|
||||
'LATIN SMALL LETTER X',
|
||||
'LATIN SMALL LETTER Y',
|
||||
'LATIN SMALL LETTER Z',
|
||||
null]);
|
||||
|
||||
assertEquals('LATIN SMALL LETTER A', alphabetNames.at(97));
|
||||
assertEquals('LATIN SMALL LETTER Y', alphabetNames.at(121));
|
||||
assertEquals(null, alphabetNames.at(140));
|
||||
assertEquals(null, alphabetNames.at(0));
|
||||
}
|
||||
|
||||
function testInversionMapSplice1() {
|
||||
var alphabetNames = newAsciiMap();
|
||||
alphabetNames.spliceInversion(
|
||||
[99, 105, 114], ['XXX', 'YYY', 'ZZZ']);
|
||||
assertEquals('LATIN SMALL LETTER B', alphabetNames.at(98));
|
||||
assertEquals('XXX', alphabetNames.at(100));
|
||||
assertEquals('ZZZ', alphabetNames.at(114));
|
||||
assertEquals('ZZZ', alphabetNames.at(119));
|
||||
assertEquals('LATIN SMALL LETTER X', alphabetNames.at(120));
|
||||
}
|
||||
|
||||
function testInversionMapSplice2() {
|
||||
var alphabetNames = newAsciiMap();
|
||||
alphabetNames.spliceInversion(
|
||||
[105, 114, 121], ['XXX', 'YYY', 'ZZZ']);
|
||||
assertEquals(null, alphabetNames.at(104));
|
||||
assertEquals('XXX', alphabetNames.at(105));
|
||||
assertEquals('YYY', alphabetNames.at(120));
|
||||
assertEquals('ZZZ', alphabetNames.at(121));
|
||||
assertEquals('LATIN SMALL LETTER Z', alphabetNames.at(122));
|
||||
}
|
||||
|
||||
function testInversionMapSplice3() {
|
||||
var alphabetNames = newAsciiMap();
|
||||
alphabetNames.spliceInversion(
|
||||
[98, 99], ['CHANGED LETTER B', 'CHANGED LETTER C']);
|
||||
assertEquals('LATIN SMALL LETTER A', alphabetNames.at(97));
|
||||
assertEquals('CHANGED LETTER B', alphabetNames.at(98));
|
||||
assertEquals('CHANGED LETTER C', alphabetNames.at(99));
|
||||
assertEquals('LATIN SMALL LETTER D', alphabetNames.at(100));
|
||||
assertEquals(null, alphabetNames.at(101));
|
||||
}
|
||||
|
||||
function testInversionMapSplice4() {
|
||||
var alphabetNames = newAsciiMap();
|
||||
alphabetNames.spliceInversion(
|
||||
[98, 1], ['CHANGED LETTER B', 'CHANGED LETTER C'],
|
||||
true /* delta mode */);
|
||||
assertEquals('LATIN SMALL LETTER A', alphabetNames.at(97));
|
||||
assertEquals('CHANGED LETTER B', alphabetNames.at(98));
|
||||
assertEquals('CHANGED LETTER C', alphabetNames.at(99));
|
||||
assertEquals('LATIN SMALL LETTER D', alphabetNames.at(100));
|
||||
assertEquals(null, alphabetNames.at(101));
|
||||
}
|
||||
|
||||
function testInversionMapSplice5() {
|
||||
var map = new goog.structs.InversionMap(
|
||||
[0, 97, 98, 99],
|
||||
[null,
|
||||
'LATIN SMALL LETTER A',
|
||||
'LATIN SMALL LETTER B',
|
||||
'LATIN SMALL LETTER C']);
|
||||
map.spliceInversion(
|
||||
[98], ['CHANGED LETTER B']);
|
||||
assertEquals('LATIN SMALL LETTER A', map.at(97));
|
||||
assertEquals('CHANGED LETTER B', map.at(98));
|
||||
assertEquals('LATIN SMALL LETTER C', map.at(99));
|
||||
|
||||
assertArrayEquals([0, 97, 98, 99], map.rangeArray);
|
||||
}
|
||||
|
||||
function newAsciiMap() {
|
||||
return new goog.structs.InversionMap(
|
||||
[0, 97, 98, 99, 100, 101, 120, 121, 122, 123],
|
||||
[null,
|
||||
'LATIN SMALL LETTER A',
|
||||
'LATIN SMALL LETTER B',
|
||||
'LATIN SMALL LETTER C',
|
||||
'LATIN SMALL LETTER D',
|
||||
null,
|
||||
'LATIN SMALL LETTER X',
|
||||
'LATIN SMALL LETTER Y',
|
||||
'LATIN SMALL LETTER Z',
|
||||
null]);
|
||||
}
|
||||
@@ -0,0 +1,488 @@
|
||||
// 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 A LinkedMap data structure that is accessed using key/value
|
||||
* pairs like an ordinary Map, but which guarantees a consistent iteration
|
||||
* order over its entries. The iteration order is either insertion order (the
|
||||
* default) or ordered from most recent to least recent use. By setting a fixed
|
||||
* size, the LRU version of the LinkedMap makes an effective object cache. This
|
||||
* data structure is similar to Java's LinkedHashMap.
|
||||
*
|
||||
* @author brenneman@google.com (Shawn Brenneman)
|
||||
*/
|
||||
|
||||
|
||||
goog.provide('goog.structs.LinkedMap');
|
||||
|
||||
goog.require('goog.structs.Map');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Class for a LinkedMap datastructure, which combines O(1) map access for
|
||||
* key/value pairs with a linked list for a consistent iteration order. Sample
|
||||
* usage:
|
||||
*
|
||||
* <pre>
|
||||
* var m = new LinkedMap();
|
||||
* m.set('param1', 'A');
|
||||
* m.set('param2', 'B');
|
||||
* m.set('param3', 'C');
|
||||
* alert(m.getKeys()); // param1, param2, param3
|
||||
*
|
||||
* var c = new LinkedMap(5, true);
|
||||
* for (var i = 0; i < 10; i++) {
|
||||
* c.set('entry' + i, false);
|
||||
* }
|
||||
* alert(c.getKeys()); // entry9, entry8, entry7, entry6, entry5
|
||||
*
|
||||
* c.set('entry5', true);
|
||||
* c.set('entry1', false);
|
||||
* alert(c.getKeys()); // entry1, entry5, entry9, entry8, entry7
|
||||
* </pre>
|
||||
*
|
||||
* @param {number=} opt_maxCount The maximum number of objects to store in the
|
||||
* LinkedMap. If unspecified or 0, there is no maximum.
|
||||
* @param {boolean=} opt_cache When set, the LinkedMap stores items in order
|
||||
* from most recently used to least recently used, instead of insertion
|
||||
* order.
|
||||
* @constructor
|
||||
* @template KEY, VALUE
|
||||
*/
|
||||
goog.structs.LinkedMap = function(opt_maxCount, opt_cache) {
|
||||
/**
|
||||
* The maximum number of entries to allow, or null if there is no limit.
|
||||
* @type {?number}
|
||||
* @private
|
||||
*/
|
||||
this.maxCount_ = opt_maxCount || null;
|
||||
|
||||
/**
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
this.cache_ = !!opt_cache;
|
||||
|
||||
/**
|
||||
* @private {!goog.structs.Map<string,
|
||||
* goog.structs.LinkedMap.Node_.<string, VALUE>>}
|
||||
*/
|
||||
this.map_ = new goog.structs.Map();
|
||||
|
||||
this.head_ = new goog.structs.LinkedMap.Node_('', undefined);
|
||||
this.head_.next = this.head_.prev = this.head_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Finds a node and updates it to be the most recently used.
|
||||
* @param {string} key The key of the node.
|
||||
* @return {goog.structs.LinkedMap.Node_} The node or null if not found.
|
||||
* @private
|
||||
*/
|
||||
goog.structs.LinkedMap.prototype.findAndMoveToTop_ = function(key) {
|
||||
var node = this.map_.get(key);
|
||||
if (node) {
|
||||
if (this.cache_) {
|
||||
node.remove();
|
||||
this.insert_(node);
|
||||
}
|
||||
}
|
||||
return node;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Retrieves the value for a given key. If this is a caching LinkedMap, the
|
||||
* entry will become the most recently used.
|
||||
* @param {string} key The key to retrieve the value for.
|
||||
* @param {VALUE=} opt_val A default value that will be returned if the key is
|
||||
* not found, defaults to undefined.
|
||||
* @return {VALUE} The retrieved value.
|
||||
*/
|
||||
goog.structs.LinkedMap.prototype.get = function(key, opt_val) {
|
||||
var node = this.findAndMoveToTop_(key);
|
||||
return node ? node.value : opt_val;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Retrieves the value for a given key without updating the entry to be the
|
||||
* most recently used.
|
||||
* @param {string} key The key to retrieve the value for.
|
||||
* @param {VALUE=} opt_val A default value that will be returned if the key is
|
||||
* not found.
|
||||
* @return {VALUE} The retrieved value.
|
||||
*/
|
||||
goog.structs.LinkedMap.prototype.peekValue = function(key, opt_val) {
|
||||
var node = this.map_.get(key);
|
||||
return node ? node.value : opt_val;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sets a value for a given key. If this is a caching LinkedMap, this entry
|
||||
* will become the most recently used.
|
||||
* @param {string} key Key with which the specified value is to be associated.
|
||||
* @param {VALUE} value Value to be associated with the specified key.
|
||||
*/
|
||||
goog.structs.LinkedMap.prototype.set = function(key, value) {
|
||||
var node = this.findAndMoveToTop_(key);
|
||||
if (node) {
|
||||
node.value = value;
|
||||
} else {
|
||||
node = new goog.structs.LinkedMap.Node_(key, value);
|
||||
this.map_.set(key, node);
|
||||
this.insert_(node);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the value of the first node without making any modifications.
|
||||
* @return {VALUE} The value of the first node or undefined if the map is empty.
|
||||
*/
|
||||
goog.structs.LinkedMap.prototype.peek = function() {
|
||||
return this.head_.next.value;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the value of the last node without making any modifications.
|
||||
* @return {VALUE} The value of the last node or undefined if the map is empty.
|
||||
*/
|
||||
goog.structs.LinkedMap.prototype.peekLast = function() {
|
||||
return this.head_.prev.value;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Removes the first node from the list and returns its value.
|
||||
* @return {VALUE} The value of the popped node, or undefined if the map was
|
||||
* empty.
|
||||
*/
|
||||
goog.structs.LinkedMap.prototype.shift = function() {
|
||||
return this.popNode_(this.head_.next);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Removes the last node from the list and returns its value.
|
||||
* @return {VALUE} The value of the popped node, or undefined if the map was
|
||||
* empty.
|
||||
*/
|
||||
goog.structs.LinkedMap.prototype.pop = function() {
|
||||
return this.popNode_(this.head_.prev);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Removes a value from the LinkedMap based on its key.
|
||||
* @param {string} key The key to remove.
|
||||
* @return {boolean} True if the entry was removed, false if the key was not
|
||||
* found.
|
||||
*/
|
||||
goog.structs.LinkedMap.prototype.remove = function(key) {
|
||||
var node = this.map_.get(key);
|
||||
if (node) {
|
||||
this.removeNode(node);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Removes a node from the {@code LinkedMap}. It can be overridden to do
|
||||
* further cleanup such as disposing of the node value.
|
||||
* @param {!goog.structs.LinkedMap.Node_} node The node to remove.
|
||||
* @protected
|
||||
*/
|
||||
goog.structs.LinkedMap.prototype.removeNode = function(node) {
|
||||
node.remove();
|
||||
this.map_.remove(node.key);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {number} The number of items currently in the LinkedMap.
|
||||
*/
|
||||
goog.structs.LinkedMap.prototype.getCount = function() {
|
||||
return this.map_.getCount();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {boolean} True if the cache is empty, false if it contains any items.
|
||||
*/
|
||||
goog.structs.LinkedMap.prototype.isEmpty = function() {
|
||||
return this.map_.isEmpty();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sets the maximum number of entries allowed in this object, truncating any
|
||||
* excess objects if necessary.
|
||||
* @param {number} maxCount The new maximum number of entries to allow.
|
||||
*/
|
||||
goog.structs.LinkedMap.prototype.setMaxCount = function(maxCount) {
|
||||
this.maxCount_ = maxCount || null;
|
||||
if (this.maxCount_ != null) {
|
||||
this.truncate_(this.maxCount_);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {!Array<string>} The list of the keys in the appropriate order for
|
||||
* this LinkedMap.
|
||||
*/
|
||||
goog.structs.LinkedMap.prototype.getKeys = function() {
|
||||
return this.map(function(val, key) {
|
||||
return key;
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {!Array<VALUE>} The list of the values in the appropriate order for
|
||||
* this LinkedMap.
|
||||
*/
|
||||
goog.structs.LinkedMap.prototype.getValues = function() {
|
||||
return this.map(function(val, key) {
|
||||
return val;
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Tests whether a provided value is currently in the LinkedMap. This does not
|
||||
* affect item ordering in cache-style LinkedMaps.
|
||||
* @param {VALUE} value The value to check for.
|
||||
* @return {boolean} Whether the value is in the LinkedMap.
|
||||
*/
|
||||
goog.structs.LinkedMap.prototype.contains = function(value) {
|
||||
return this.some(function(el) {
|
||||
return el == value;
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Tests whether a provided key is currently in the LinkedMap. This does not
|
||||
* affect item ordering in cache-style LinkedMaps.
|
||||
* @param {string} key The key to check for.
|
||||
* @return {boolean} Whether the key is in the LinkedMap.
|
||||
*/
|
||||
goog.structs.LinkedMap.prototype.containsKey = function(key) {
|
||||
return this.map_.containsKey(key);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Removes all entries in this object.
|
||||
*/
|
||||
goog.structs.LinkedMap.prototype.clear = function() {
|
||||
this.truncate_(0);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Calls a function on each item in the LinkedMap.
|
||||
*
|
||||
* @see goog.structs.forEach
|
||||
* @param {function(this:T, VALUE, KEY, goog.structs.LinkedMap<KEY,VALUE>)} f
|
||||
* @param {T=} opt_obj The value of "this" inside f.
|
||||
* @template T
|
||||
*/
|
||||
goog.structs.LinkedMap.prototype.forEach = function(f, opt_obj) {
|
||||
for (var n = this.head_.next; n != this.head_; n = n.next) {
|
||||
f.call(opt_obj, n.value, n.key, this);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Calls a function on each item in the LinkedMap and returns the results of
|
||||
* those calls in an array.
|
||||
*
|
||||
* @see goog.structs.map
|
||||
* @param {function(this:T, VALUE, KEY,
|
||||
* goog.structs.LinkedMap<KEY,VALUE>): RESULT} f
|
||||
* The function to call for each item. The function takes
|
||||
* three arguments: the value, the key, and the LinkedMap.
|
||||
* @param {T=} opt_obj The object context to use as "this" for the
|
||||
* function.
|
||||
* @return {!Array<RESULT>} The results of the function calls for each item in
|
||||
* the LinkedMap.
|
||||
* @template T,RESULT
|
||||
*/
|
||||
goog.structs.LinkedMap.prototype.map = function(f, opt_obj) {
|
||||
var rv = [];
|
||||
for (var n = this.head_.next; n != this.head_; n = n.next) {
|
||||
rv.push(f.call(opt_obj, n.value, n.key, this));
|
||||
}
|
||||
return rv;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Calls a function on each item in the LinkedMap and returns true if any of
|
||||
* those function calls returns a true-like value.
|
||||
*
|
||||
* @see goog.structs.some
|
||||
* @param {function(this:T, VALUE, KEY,
|
||||
* goog.structs.LinkedMap<KEY,VALUE>):boolean} f
|
||||
* The function to call for each item. The function takes
|
||||
* three arguments: the value, the key, and the LinkedMap, and returns a
|
||||
* boolean.
|
||||
* @param {T=} opt_obj The object context to use as "this" for the
|
||||
* function.
|
||||
* @return {boolean} Whether f evaluates to true for at least one item in the
|
||||
* LinkedMap.
|
||||
* @template T
|
||||
*/
|
||||
goog.structs.LinkedMap.prototype.some = function(f, opt_obj) {
|
||||
for (var n = this.head_.next; n != this.head_; n = n.next) {
|
||||
if (f.call(opt_obj, n.value, n.key, this)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Calls a function on each item in the LinkedMap and returns true only if every
|
||||
* function call returns a true-like value.
|
||||
*
|
||||
* @see goog.structs.some
|
||||
* @param {function(this:T, VALUE, KEY,
|
||||
* goog.structs.LinkedMap<KEY,VALUE>):boolean} f
|
||||
* The function to call for each item. The function takes
|
||||
* three arguments: the value, the key, and the Cache, and returns a
|
||||
* boolean.
|
||||
* @param {T=} opt_obj The object context to use as "this" for the
|
||||
* function.
|
||||
* @return {boolean} Whether f evaluates to true for every item in the Cache.
|
||||
* @template T
|
||||
*/
|
||||
goog.structs.LinkedMap.prototype.every = function(f, opt_obj) {
|
||||
for (var n = this.head_.next; n != this.head_; n = n.next) {
|
||||
if (!f.call(opt_obj, n.value, n.key, this)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Appends a node to the list. LinkedMap in cache mode adds new nodes to
|
||||
* the head of the list, otherwise they are appended to the tail. If there is a
|
||||
* maximum size, the list will be truncated if necessary.
|
||||
*
|
||||
* @param {goog.structs.LinkedMap.Node_} node The item to insert.
|
||||
* @private
|
||||
*/
|
||||
goog.structs.LinkedMap.prototype.insert_ = function(node) {
|
||||
if (this.cache_) {
|
||||
node.next = this.head_.next;
|
||||
node.prev = this.head_;
|
||||
|
||||
this.head_.next = node;
|
||||
node.next.prev = node;
|
||||
} else {
|
||||
node.prev = this.head_.prev;
|
||||
node.next = this.head_;
|
||||
|
||||
this.head_.prev = node;
|
||||
node.prev.next = node;
|
||||
}
|
||||
|
||||
if (this.maxCount_ != null) {
|
||||
this.truncate_(this.maxCount_);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Removes elements from the LinkedMap if the given count has been exceeded.
|
||||
* In cache mode removes nodes from the tail of the list. Otherwise removes
|
||||
* nodes from the head.
|
||||
* @param {number} count Number of elements to keep.
|
||||
* @private
|
||||
*/
|
||||
goog.structs.LinkedMap.prototype.truncate_ = function(count) {
|
||||
for (var i = this.map_.getCount(); i > count; i--) {
|
||||
this.removeNode(this.cache_ ? this.head_.prev : this.head_.next);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Removes the node from the LinkedMap if it is not the head, and returns
|
||||
* the node's value.
|
||||
* @param {!goog.structs.LinkedMap.Node_} node The item to remove.
|
||||
* @return {VALUE} The value of the popped node.
|
||||
* @private
|
||||
*/
|
||||
goog.structs.LinkedMap.prototype.popNode_ = function(node) {
|
||||
if (this.head_ != node) {
|
||||
this.removeNode(node);
|
||||
}
|
||||
return node.value;
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Internal class for a doubly-linked list node containing a key/value pair.
|
||||
* @param {KEY} key The key.
|
||||
* @param {VALUE} value The value.
|
||||
* @constructor
|
||||
* @template KEY, VALUE
|
||||
* @private
|
||||
*/
|
||||
goog.structs.LinkedMap.Node_ = function(key, value) {
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* The next node in the list.
|
||||
* @type {!goog.structs.LinkedMap.Node_}
|
||||
*/
|
||||
goog.structs.LinkedMap.Node_.prototype.next;
|
||||
|
||||
|
||||
/**
|
||||
* The previous node in the list.
|
||||
* @type {!goog.structs.LinkedMap.Node_}
|
||||
*/
|
||||
goog.structs.LinkedMap.Node_.prototype.prev;
|
||||
|
||||
|
||||
/**
|
||||
* Causes this node to remove itself from the list.
|
||||
*/
|
||||
goog.structs.LinkedMap.Node_.prototype.remove = function() {
|
||||
this.prev.next = this.next;
|
||||
this.next.prev = this.prev;
|
||||
|
||||
delete this.prev;
|
||||
delete this.next;
|
||||
};
|
||||
@@ -0,0 +1,22 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
Copyright 2006 The Closure Library Authors. All Rights Reserved.
|
||||
|
||||
Use of this source code is governed by the Apache License, Version 2.0.
|
||||
See the COPYING file for details.
|
||||
-->
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<title>
|
||||
Closure Unit Tests - goog.structs.LinkedMap
|
||||
</title>
|
||||
<script src="../base.js">
|
||||
</script>
|
||||
<script>
|
||||
goog.require('goog.structs.LinkedMapTest');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,296 @@
|
||||
// Copyright 2006 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.
|
||||
|
||||
goog.provide('goog.structs.LinkedMapTest');
|
||||
goog.setTestOnly('goog.structs.LinkedMapTest');
|
||||
|
||||
goog.require('goog.structs.LinkedMap');
|
||||
goog.require('goog.testing.jsunit');
|
||||
goog.require('goog.testing.recordFunction');
|
||||
|
||||
function fillLinkedMap(m) {
|
||||
m.set('a', 0);
|
||||
m.set('b', 1);
|
||||
m.set('c', 2);
|
||||
m.set('d', 3);
|
||||
}
|
||||
|
||||
var someObj = {};
|
||||
|
||||
function testLinkedMap() {
|
||||
var m = new goog.structs.LinkedMap();
|
||||
fillLinkedMap(m);
|
||||
|
||||
assertArrayEquals(['a', 'b', 'c', 'd'], m.getKeys());
|
||||
assertArrayEquals([0, 1, 2, 3], m.getValues());
|
||||
}
|
||||
|
||||
function testMaxSizeLinkedMap() {
|
||||
var m = new goog.structs.LinkedMap(3);
|
||||
fillLinkedMap(m);
|
||||
|
||||
assertArrayEquals(['b', 'c', 'd'], m.getKeys());
|
||||
assertArrayEquals([1, 2, 3], m.getValues());
|
||||
}
|
||||
|
||||
function testLruLinkedMap() {
|
||||
var m = new goog.structs.LinkedMap(undefined, true);
|
||||
fillLinkedMap(m);
|
||||
|
||||
assertArrayEquals(['d', 'c', 'b', 'a'], m.getKeys());
|
||||
assertArrayEquals([3, 2, 1, 0], m.getValues());
|
||||
|
||||
m.get('a');
|
||||
assertArrayEquals(['a', 'd', 'c', 'b'], m.getKeys());
|
||||
assertArrayEquals([0, 3, 2, 1], m.getValues());
|
||||
|
||||
m.set('b', 4);
|
||||
assertArrayEquals(['b', 'a', 'd', 'c'], m.getKeys());
|
||||
assertArrayEquals([4, 0, 3, 2], m.getValues());
|
||||
}
|
||||
|
||||
function testMaxSizeLruLinkedMap() {
|
||||
var m = new goog.structs.LinkedMap(3, true);
|
||||
fillLinkedMap(m);
|
||||
|
||||
assertArrayEquals(['d', 'c', 'b'], m.getKeys());
|
||||
assertArrayEquals([3, 2, 1], m.getValues());
|
||||
|
||||
m.get('c');
|
||||
assertArrayEquals(['c', 'd', 'b'], m.getKeys());
|
||||
assertArrayEquals([2, 3, 1], m.getValues());
|
||||
|
||||
m.set('d', 4);
|
||||
assertArrayEquals(['d', 'c', 'b'], m.getKeys());
|
||||
assertArrayEquals([4, 2, 1], m.getValues());
|
||||
}
|
||||
|
||||
function testGetCount() {
|
||||
var m = new goog.structs.LinkedMap();
|
||||
assertEquals(0, m.getCount());
|
||||
m.set('a', 0);
|
||||
assertEquals(1, m.getCount());
|
||||
m.set('a', 1);
|
||||
assertEquals(1, m.getCount());
|
||||
m.set('b', 2);
|
||||
assertEquals(2, m.getCount());
|
||||
m.remove('a');
|
||||
assertEquals(1, m.getCount());
|
||||
}
|
||||
|
||||
function testIsEmpty() {
|
||||
var m = new goog.structs.LinkedMap();
|
||||
assertTrue(m.isEmpty());
|
||||
m.set('a', 0);
|
||||
assertFalse(m.isEmpty());
|
||||
m.remove('a');
|
||||
assertTrue(m.isEmpty());
|
||||
}
|
||||
|
||||
function testSetMaxCount() {
|
||||
var m = new goog.structs.LinkedMap(3);
|
||||
fillLinkedMap(m);
|
||||
assertEquals(3, m.getCount());
|
||||
|
||||
m.setMaxCount(5);
|
||||
m.set('e', 5);
|
||||
m.set('f', 6);
|
||||
m.set('g', 7);
|
||||
assertEquals(5, m.getCount());
|
||||
|
||||
m.setMaxCount(4);
|
||||
assertEquals(4, m.getCount());
|
||||
|
||||
m.setMaxCount(0);
|
||||
m.set('h', 8);
|
||||
m.set('i', 9);
|
||||
m.set('j', 10);
|
||||
assertEquals(7, m.getCount());
|
||||
}
|
||||
|
||||
function testClear() {
|
||||
var m = new goog.structs.LinkedMap();
|
||||
fillLinkedMap(m);
|
||||
m.clear();
|
||||
assertTrue(m.isEmpty());
|
||||
}
|
||||
|
||||
function testForEach() {
|
||||
var m = new goog.structs.LinkedMap();
|
||||
fillLinkedMap(m);
|
||||
|
||||
m.forEach(function(val, key, linkedMap) {
|
||||
linkedMap.set(key, val * 2);
|
||||
assertEquals('forEach should run in provided context.', someObj, this);
|
||||
}, someObj);
|
||||
|
||||
assertArrayEquals(['a', 'b', 'c', 'd'], m.getKeys());
|
||||
assertArrayEquals([0, 2, 4, 6], m.getValues());
|
||||
}
|
||||
|
||||
function testMap() {
|
||||
var m = new goog.structs.LinkedMap();
|
||||
fillLinkedMap(m);
|
||||
|
||||
var result = m.map(function(val, key, linkedMap) {
|
||||
assertEquals('The LinkedMap object should get passed in', m, linkedMap);
|
||||
assertEquals('map should run in provided context', someObj, this);
|
||||
return key + val;
|
||||
}, someObj);
|
||||
|
||||
assertArrayEquals(['a0', 'b1', 'c2', 'd3'], result);
|
||||
}
|
||||
|
||||
function testSome() {
|
||||
var m = new goog.structs.LinkedMap();
|
||||
fillLinkedMap(m);
|
||||
|
||||
var result = m.some(function(val, key, linkedMap) {
|
||||
assertEquals('The LinkedMap object should get passed in', m, linkedMap);
|
||||
assertEquals('map should run in provided context', someObj, this);
|
||||
return val > 2;
|
||||
}, someObj);
|
||||
|
||||
assertTrue(result);
|
||||
assertFalse(m.some(function(val) {return val > 3}));
|
||||
|
||||
assertTrue(m.some(function(val, key) {return key == 'c';}));
|
||||
assertFalse(m.some(function(val, key) {return key == 'e';}));
|
||||
}
|
||||
|
||||
function testEvery() {
|
||||
var m = new goog.structs.LinkedMap();
|
||||
fillLinkedMap(m);
|
||||
|
||||
var result = m.every(function(val, key, linkedMap) {
|
||||
assertEquals('The LinkedMap object should get passed in', m, linkedMap);
|
||||
assertEquals('map should run in provided context', someObj, this);
|
||||
return val < 5;
|
||||
}, someObj);
|
||||
|
||||
assertTrue(result);
|
||||
assertFalse(m.every(function(val) {return val < 2}));
|
||||
|
||||
assertTrue(m.every(function(val, key) {return key.length == 1;}));
|
||||
assertFalse(m.every(function(val, key) {return key == 'b';}));
|
||||
}
|
||||
|
||||
function testPeek() {
|
||||
var m = new goog.structs.LinkedMap();
|
||||
assertEquals(undefined, m.peek());
|
||||
assertEquals(undefined, m.peekLast());
|
||||
|
||||
fillLinkedMap(m);
|
||||
assertEquals(0, m.peek());
|
||||
|
||||
m.remove('a');
|
||||
assertEquals(1, m.peek());
|
||||
|
||||
assertEquals(3, m.peekLast());
|
||||
|
||||
assertEquals(3, m.peekValue('d'));
|
||||
assertEquals(1, m.peek());
|
||||
|
||||
m.remove('d');
|
||||
assertEquals(2, m.peekLast());
|
||||
}
|
||||
|
||||
function testPop() {
|
||||
var m = new goog.structs.LinkedMap();
|
||||
assertEquals(undefined, m.shift());
|
||||
assertEquals(undefined, m.pop());
|
||||
|
||||
fillLinkedMap(m);
|
||||
assertEquals(4, m.getCount());
|
||||
|
||||
assertEquals(0, m.shift());
|
||||
assertEquals(1, m.peek());
|
||||
|
||||
assertEquals(3, m.pop());
|
||||
assertEquals(2, m.peekLast());
|
||||
|
||||
assertEquals(2, m.getCount());
|
||||
}
|
||||
|
||||
function testContains() {
|
||||
var m = new goog.structs.LinkedMap();
|
||||
fillLinkedMap(m);
|
||||
|
||||
assertTrue(m.contains(2));
|
||||
assertFalse(m.contains(4));
|
||||
}
|
||||
|
||||
function testContainsKey() {
|
||||
var m = new goog.structs.LinkedMap();
|
||||
fillLinkedMap(m);
|
||||
|
||||
assertTrue(m.containsKey('b'));
|
||||
assertFalse(m.containsKey('elephant'));
|
||||
assertFalse(m.containsKey('undefined'));
|
||||
}
|
||||
|
||||
function testRemoveNodeCalls() {
|
||||
var m = new goog.structs.LinkedMap(1);
|
||||
m.removeNode = goog.testing.recordFunction(m.removeNode);
|
||||
|
||||
m.set('1', 1);
|
||||
assertEquals('removeNode not called after adding an element', 0,
|
||||
m.removeNode.getCallCount());
|
||||
m.set('1', 2);
|
||||
assertEquals('removeNode not called after updating an element', 0,
|
||||
m.removeNode.getCallCount());
|
||||
m.set('2', 2);
|
||||
assertEquals('removeNode called after adding an overflowing element', 1,
|
||||
m.removeNode.getCallCount());
|
||||
|
||||
m.remove('3');
|
||||
assertEquals('removeNode not called after removing a non-existing element', 1,
|
||||
m.removeNode.getCallCount());
|
||||
m.remove('2');
|
||||
assertEquals('removeNode called after removing an existing element', 2,
|
||||
m.removeNode.getCallCount());
|
||||
|
||||
m.set('1', 1);
|
||||
m.clear();
|
||||
assertEquals('removeNode called after clearing the map', 3,
|
||||
m.removeNode.getCallCount());
|
||||
m.clear();
|
||||
assertEquals('removeNode not called after clearing an empty map', 3,
|
||||
m.removeNode.getCallCount());
|
||||
|
||||
m.set('1', 1);
|
||||
m.pop();
|
||||
assertEquals('removeNode called after calling pop', 4,
|
||||
m.removeNode.getCallCount());
|
||||
m.pop();
|
||||
assertEquals('removeNode not called after calling pop on an empty map', 4,
|
||||
m.removeNode.getCallCount());
|
||||
|
||||
m.set('1', 1);
|
||||
m.shift();
|
||||
assertEquals('removeNode called after calling shift', 5,
|
||||
m.removeNode.getCallCount());
|
||||
m.shift();
|
||||
assertEquals('removeNode not called after calling shift on an empty map', 5,
|
||||
m.removeNode.getCallCount());
|
||||
|
||||
m.setMaxCount(2);
|
||||
m.set('1', 1);
|
||||
m.set('2', 2);
|
||||
assertEquals('removeNode not called after increasing the maximum map size', 5,
|
||||
m.removeNode.getCallCount());
|
||||
m.setMaxCount(1);
|
||||
assertEquals('removeNode called after decreasing the maximum map size', 6,
|
||||
m.removeNode.getCallCount());
|
||||
}
|
||||
@@ -0,0 +1,460 @@
|
||||
// Copyright 2006 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: Hash Map.
|
||||
*
|
||||
* @author arv@google.com (Erik Arvidsson)
|
||||
*
|
||||
* This file contains an implementation of a Map structure. It implements a lot
|
||||
* of the methods used in goog.structs so those functions work on hashes. This
|
||||
* is best suited for complex key types. For simple keys such as numbers and
|
||||
* strings, and where special names like __proto__ are not a concern, consider
|
||||
* using the lighter-weight utilities in goog.object.
|
||||
*/
|
||||
|
||||
|
||||
goog.provide('goog.structs.Map');
|
||||
|
||||
goog.require('goog.iter.Iterator');
|
||||
goog.require('goog.iter.StopIteration');
|
||||
goog.require('goog.object');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Class for Hash Map datastructure.
|
||||
* @param {*=} opt_map Map or Object to initialize the map with.
|
||||
* @param {...*} var_args If 2 or more arguments are present then they
|
||||
* will be used as key-value pairs.
|
||||
* @constructor
|
||||
* @template K, V
|
||||
*/
|
||||
goog.structs.Map = function(opt_map, var_args) {
|
||||
|
||||
/**
|
||||
* Underlying JS object used to implement the map.
|
||||
* @private {!Object}
|
||||
*/
|
||||
this.map_ = {};
|
||||
|
||||
/**
|
||||
* An array of keys. This is necessary for two reasons:
|
||||
* 1. Iterating the keys using for (var key in this.map_) allocates an
|
||||
* object for every key in IE which is really bad for IE6 GC perf.
|
||||
* 2. Without a side data structure, we would need to escape all the keys
|
||||
* as that would be the only way we could tell during iteration if the
|
||||
* key was an internal key or a property of the object.
|
||||
*
|
||||
* This array can contain deleted keys so it's necessary to check the map
|
||||
* as well to see if the key is still in the map (this doesn't require a
|
||||
* memory allocation in IE).
|
||||
* @private {!Array<string>}
|
||||
*/
|
||||
this.keys_ = [];
|
||||
|
||||
/**
|
||||
* The number of key value pairs in the map.
|
||||
* @private {number}
|
||||
*/
|
||||
this.count_ = 0;
|
||||
|
||||
/**
|
||||
* Version used to detect changes while iterating.
|
||||
* @private {number}
|
||||
*/
|
||||
this.version_ = 0;
|
||||
|
||||
var argLength = arguments.length;
|
||||
|
||||
if (argLength > 1) {
|
||||
if (argLength % 2) {
|
||||
throw Error('Uneven number of arguments');
|
||||
}
|
||||
for (var i = 0; i < argLength; i += 2) {
|
||||
this.set(arguments[i], arguments[i + 1]);
|
||||
}
|
||||
} else if (opt_map) {
|
||||
this.addAll(/** @type {Object} */ (opt_map));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {number} The number of key-value pairs in the map.
|
||||
*/
|
||||
goog.structs.Map.prototype.getCount = function() {
|
||||
return this.count_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the values of the map.
|
||||
* @return {!Array<V>} The values in the map.
|
||||
*/
|
||||
goog.structs.Map.prototype.getValues = function() {
|
||||
this.cleanupKeysArray_();
|
||||
|
||||
var rv = [];
|
||||
for (var i = 0; i < this.keys_.length; i++) {
|
||||
var key = this.keys_[i];
|
||||
rv.push(this.map_[key]);
|
||||
}
|
||||
return rv;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the keys of the map.
|
||||
* @return {!Array<string>} Array of string values.
|
||||
*/
|
||||
goog.structs.Map.prototype.getKeys = function() {
|
||||
this.cleanupKeysArray_();
|
||||
return /** @type {!Array<string>} */ (this.keys_.concat());
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Whether the map contains the given key.
|
||||
* @param {*} key The key to check for.
|
||||
* @return {boolean} Whether the map contains the key.
|
||||
*/
|
||||
goog.structs.Map.prototype.containsKey = function(key) {
|
||||
return goog.structs.Map.hasKey_(this.map_, key);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Whether the map contains the given value. This is O(n).
|
||||
* @param {V} val The value to check for.
|
||||
* @return {boolean} Whether the map contains the value.
|
||||
*/
|
||||
goog.structs.Map.prototype.containsValue = function(val) {
|
||||
for (var i = 0; i < this.keys_.length; i++) {
|
||||
var key = this.keys_[i];
|
||||
if (goog.structs.Map.hasKey_(this.map_, key) && this.map_[key] == val) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Whether this map is equal to the argument map.
|
||||
* @param {goog.structs.Map} otherMap The map against which to test equality.
|
||||
* @param {function(V, V): boolean=} opt_equalityFn Optional equality function
|
||||
* to test equality of values. If not specified, this will test whether
|
||||
* the values contained in each map are identical objects.
|
||||
* @return {boolean} Whether the maps are equal.
|
||||
*/
|
||||
goog.structs.Map.prototype.equals = function(otherMap, opt_equalityFn) {
|
||||
if (this === otherMap) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.count_ != otherMap.getCount()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var equalityFn = opt_equalityFn || goog.structs.Map.defaultEquals;
|
||||
|
||||
this.cleanupKeysArray_();
|
||||
for (var key, i = 0; key = this.keys_[i]; i++) {
|
||||
if (!equalityFn(this.get(key), otherMap.get(key))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Default equality test for values.
|
||||
* @param {*} a The first value.
|
||||
* @param {*} b The second value.
|
||||
* @return {boolean} Whether a and b reference the same object.
|
||||
*/
|
||||
goog.structs.Map.defaultEquals = function(a, b) {
|
||||
return a === b;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {boolean} Whether the map is empty.
|
||||
*/
|
||||
goog.structs.Map.prototype.isEmpty = function() {
|
||||
return this.count_ == 0;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Removes all key-value pairs from the map.
|
||||
*/
|
||||
goog.structs.Map.prototype.clear = function() {
|
||||
this.map_ = {};
|
||||
this.keys_.length = 0;
|
||||
this.count_ = 0;
|
||||
this.version_ = 0;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Removes a key-value pair based on the key. This is O(logN) amortized due to
|
||||
* updating the keys array whenever the count becomes half the size of the keys
|
||||
* in the keys array.
|
||||
* @param {*} key The key to remove.
|
||||
* @return {boolean} Whether object was removed.
|
||||
*/
|
||||
goog.structs.Map.prototype.remove = function(key) {
|
||||
if (goog.structs.Map.hasKey_(this.map_, key)) {
|
||||
delete this.map_[key];
|
||||
this.count_--;
|
||||
this.version_++;
|
||||
|
||||
// clean up the keys array if the threshhold is hit
|
||||
if (this.keys_.length > 2 * this.count_) {
|
||||
this.cleanupKeysArray_();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Cleans up the temp keys array by removing entries that are no longer in the
|
||||
* map.
|
||||
* @private
|
||||
*/
|
||||
goog.structs.Map.prototype.cleanupKeysArray_ = function() {
|
||||
if (this.count_ != this.keys_.length) {
|
||||
// First remove keys that are no longer in the map.
|
||||
var srcIndex = 0;
|
||||
var destIndex = 0;
|
||||
while (srcIndex < this.keys_.length) {
|
||||
var key = this.keys_[srcIndex];
|
||||
if (goog.structs.Map.hasKey_(this.map_, key)) {
|
||||
this.keys_[destIndex++] = key;
|
||||
}
|
||||
srcIndex++;
|
||||
}
|
||||
this.keys_.length = destIndex;
|
||||
}
|
||||
|
||||
if (this.count_ != this.keys_.length) {
|
||||
// If the count still isn't correct, that means we have duplicates. This can
|
||||
// happen when the same key is added and removed multiple times. Now we have
|
||||
// to allocate one extra Object to remove the duplicates. This could have
|
||||
// been done in the first pass, but in the common case, we can avoid
|
||||
// allocating an extra object by only doing this when necessary.
|
||||
var seen = {};
|
||||
var srcIndex = 0;
|
||||
var destIndex = 0;
|
||||
while (srcIndex < this.keys_.length) {
|
||||
var key = this.keys_[srcIndex];
|
||||
if (!(goog.structs.Map.hasKey_(seen, key))) {
|
||||
this.keys_[destIndex++] = key;
|
||||
seen[key] = 1;
|
||||
}
|
||||
srcIndex++;
|
||||
}
|
||||
this.keys_.length = destIndex;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the value for the given key. If the key is not found and the default
|
||||
* value is not given this will return {@code undefined}.
|
||||
* @param {*} key The key to get the value for.
|
||||
* @param {DEFAULT=} opt_val The value to return if no item is found for the
|
||||
* given key, defaults to undefined.
|
||||
* @return {V|DEFAULT} The value for the given key.
|
||||
* @template DEFAULT
|
||||
*/
|
||||
goog.structs.Map.prototype.get = function(key, opt_val) {
|
||||
if (goog.structs.Map.hasKey_(this.map_, key)) {
|
||||
return this.map_[key];
|
||||
}
|
||||
return opt_val;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Adds a key-value pair to the map.
|
||||
* @param {*} key The key.
|
||||
* @param {V} value The value to add.
|
||||
* @return {*} Some subclasses return a value.
|
||||
*/
|
||||
goog.structs.Map.prototype.set = function(key, value) {
|
||||
if (!(goog.structs.Map.hasKey_(this.map_, key))) {
|
||||
this.count_++;
|
||||
this.keys_.push(key);
|
||||
// Only change the version if we add a new key.
|
||||
this.version_++;
|
||||
}
|
||||
this.map_[key] = value;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Adds multiple key-value pairs from another goog.structs.Map or Object.
|
||||
* @param {Object} map Object containing the data to add.
|
||||
*/
|
||||
goog.structs.Map.prototype.addAll = function(map) {
|
||||
var keys, values;
|
||||
if (map instanceof goog.structs.Map) {
|
||||
keys = map.getKeys();
|
||||
values = map.getValues();
|
||||
} else {
|
||||
keys = goog.object.getKeys(map);
|
||||
values = goog.object.getValues(map);
|
||||
}
|
||||
// we could use goog.array.forEach here but I don't want to introduce that
|
||||
// dependency just for this.
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
this.set(keys[i], values[i]);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Calls the given function on each entry in the map.
|
||||
* @param {function(this:T, V, K, goog.structs.Map<K,V>)} f
|
||||
* @param {T=} opt_obj The value of "this" inside f.
|
||||
* @template T
|
||||
*/
|
||||
goog.structs.Map.prototype.forEach = function(f, opt_obj) {
|
||||
var keys = this.getKeys();
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
var key = keys[i];
|
||||
var value = this.get(key);
|
||||
f.call(opt_obj, value, key, this);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Clones a map and returns a new map.
|
||||
* @return {!goog.structs.Map} A new map with the same key-value pairs.
|
||||
*/
|
||||
goog.structs.Map.prototype.clone = function() {
|
||||
return new goog.structs.Map(this);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns a new map in which all the keys and values are interchanged
|
||||
* (keys become values and values become keys). If multiple keys map to the
|
||||
* same value, the chosen transposed value is implementation-dependent.
|
||||
*
|
||||
* It acts very similarly to {goog.object.transpose(Object)}.
|
||||
*
|
||||
* @return {!goog.structs.Map} The transposed map.
|
||||
*/
|
||||
goog.structs.Map.prototype.transpose = function() {
|
||||
var transposed = new goog.structs.Map();
|
||||
for (var i = 0; i < this.keys_.length; i++) {
|
||||
var key = this.keys_[i];
|
||||
var value = this.map_[key];
|
||||
transposed.set(value, key);
|
||||
}
|
||||
|
||||
return transposed;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {!Object} Object representation of the map.
|
||||
*/
|
||||
goog.structs.Map.prototype.toObject = function() {
|
||||
this.cleanupKeysArray_();
|
||||
var obj = {};
|
||||
for (var i = 0; i < this.keys_.length; i++) {
|
||||
var key = this.keys_[i];
|
||||
obj[key] = this.map_[key];
|
||||
}
|
||||
return obj;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns an iterator that iterates over the keys in the map. Removal of keys
|
||||
* while iterating might have undesired side effects.
|
||||
* @return {!goog.iter.Iterator} An iterator over the keys in the map.
|
||||
*/
|
||||
goog.structs.Map.prototype.getKeyIterator = function() {
|
||||
return this.__iterator__(true);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns an iterator that iterates over the values in the map. Removal of
|
||||
* keys while iterating might have undesired side effects.
|
||||
* @return {!goog.iter.Iterator} An iterator over the values in the map.
|
||||
*/
|
||||
goog.structs.Map.prototype.getValueIterator = function() {
|
||||
return this.__iterator__(false);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns an iterator that iterates over the values or the keys in the map.
|
||||
* This throws an exception if the map was mutated since the iterator was
|
||||
* created.
|
||||
* @param {boolean=} opt_keys True to iterate over the keys. False to iterate
|
||||
* over the values. The default value is false.
|
||||
* @return {!goog.iter.Iterator} An iterator over the values or keys in the map.
|
||||
*/
|
||||
goog.structs.Map.prototype.__iterator__ = function(opt_keys) {
|
||||
// Clean up keys to minimize the risk of iterating over dead keys.
|
||||
this.cleanupKeysArray_();
|
||||
|
||||
var i = 0;
|
||||
var keys = this.keys_;
|
||||
var map = this.map_;
|
||||
var version = this.version_;
|
||||
var selfObj = this;
|
||||
|
||||
var newIter = new goog.iter.Iterator;
|
||||
newIter.next = function() {
|
||||
while (true) {
|
||||
if (version != selfObj.version_) {
|
||||
throw Error('The map has changed since the iterator was created');
|
||||
}
|
||||
if (i >= keys.length) {
|
||||
throw goog.iter.StopIteration;
|
||||
}
|
||||
var key = keys[i++];
|
||||
return opt_keys ? key : map[key];
|
||||
}
|
||||
};
|
||||
return newIter;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Safe way to test for hasOwnProperty. It even allows testing for
|
||||
* 'hasOwnProperty'.
|
||||
* @param {Object} obj The object to test for presence of the given key.
|
||||
* @param {*} key The key to check for.
|
||||
* @return {boolean} Whether the object has the key.
|
||||
* @private
|
||||
*/
|
||||
goog.structs.Map.hasKey_ = function(obj, key) {
|
||||
return Object.prototype.hasOwnProperty.call(obj, key);
|
||||
};
|
||||
@@ -0,0 +1,22 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
Copyright 2006 The Closure Library Authors. All Rights Reserved.
|
||||
|
||||
Use of this source code is governed by the Apache License, Version 2.0.
|
||||
See the COPYING file for details.
|
||||
-->
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<title>
|
||||
Closure Unit Tests - goog.structs.Map
|
||||
</title>
|
||||
<script src="../base.js">
|
||||
</script>
|
||||
<script>
|
||||
goog.require('goog.structs.MapTest');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,426 @@
|
||||
// Copyright 2006 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.
|
||||
|
||||
goog.provide('goog.structs.MapTest');
|
||||
goog.setTestOnly('goog.structs.MapTest');
|
||||
|
||||
goog.require('goog.iter');
|
||||
goog.require('goog.structs');
|
||||
goog.require('goog.structs.Map');
|
||||
goog.require('goog.testing.jsunit');
|
||||
|
||||
function stringifyMap(m) {
|
||||
var keys = goog.structs.getKeys(m);
|
||||
var s = '';
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
s += keys[i] + m[keys[i]];
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
function getMap() {
|
||||
var m = new goog.structs.Map;
|
||||
m.set('a', 0);
|
||||
m.set('b', 1);
|
||||
m.set('c', 2);
|
||||
m.set('d', 3);
|
||||
return m;
|
||||
}
|
||||
|
||||
function testGetCount() {
|
||||
var m = getMap();
|
||||
assertEquals('count, should be 4', m.getCount(), 4);
|
||||
m.remove('d');
|
||||
assertEquals('count, should be 3', m.getCount(), 3);
|
||||
}
|
||||
|
||||
function testKeys() {
|
||||
var m = getMap();
|
||||
assertEquals('getKeys, The keys should be a,b,c', m.getKeys().join(','),
|
||||
'a,b,c,d');
|
||||
}
|
||||
|
||||
function testValues() {
|
||||
var m = getMap();
|
||||
assertEquals('getValues, The values should be 0,1,2',
|
||||
m.getValues().join(','), '0,1,2,3');
|
||||
}
|
||||
|
||||
function testContainsKey() {
|
||||
var m = getMap();
|
||||
assertTrue("containsKey, Should contain the 'a' key", m.containsKey('a'));
|
||||
assertFalse("containsKey, Should not contain the 'e' key",
|
||||
m.containsKey('e'));
|
||||
}
|
||||
|
||||
function testClear() {
|
||||
var m = getMap();
|
||||
m.clear();
|
||||
assertTrue('cleared so it should be empty', m.isEmpty());
|
||||
assertTrue("cleared so it should not contain 'a' key", !m.containsKey('a'));
|
||||
}
|
||||
|
||||
function testAddAll() {
|
||||
var m = new goog.structs.Map;
|
||||
m.addAll({a: 0, b: 1, c: 2, d: 3});
|
||||
assertTrue('addAll so it should not be empty', !m.isEmpty());
|
||||
assertTrue("addAll so it should contain 'c' key", m.containsKey('c'));
|
||||
|
||||
var m2 = new goog.structs.Map;
|
||||
m2.addAll(m);
|
||||
assertTrue('addAll so it should not be empty', !m2.isEmpty());
|
||||
assertTrue("addAll so it should contain 'c' key", m2.containsKey('c'));
|
||||
}
|
||||
|
||||
function testConstructor() {
|
||||
var m = getMap();
|
||||
var m2 = new goog.structs.Map(m);
|
||||
assertTrue('constr with Map so it should not be empty', !m2.isEmpty());
|
||||
assertTrue("constr with Map so it should contain 'c' key",
|
||||
m2.containsKey('c'));
|
||||
}
|
||||
|
||||
|
||||
function testConstructorWithVarArgs() {
|
||||
var m = new goog.structs.Map('a', 1);
|
||||
assertTrue('constr with var_args so it should not be empty', !m.isEmpty());
|
||||
assertEquals('constr with var_args', 1, m.get('a'));
|
||||
|
||||
m = new goog.structs.Map('a', 1, 'b', 2);
|
||||
assertTrue('constr with var_args so it should not be empty', !m.isEmpty());
|
||||
assertEquals('constr with var_args', 1, m.get('a'));
|
||||
assertEquals('constr with var_args', 2, m.get('b'));
|
||||
|
||||
assertThrows('Odd number of arguments is not allowed', function() {
|
||||
var m = new goog.structs.Map('a', 1, 'b');
|
||||
});
|
||||
}
|
||||
|
||||
function testClone() {
|
||||
var m = getMap();
|
||||
var m2 = m.clone();
|
||||
assertTrue('clone so it should not be empty', !m2.isEmpty());
|
||||
assertTrue("clone so it should contain 'c' key", m2.containsKey('c'));
|
||||
}
|
||||
|
||||
|
||||
function testRemove() {
|
||||
var m = new goog.structs.Map();
|
||||
for (var i = 0; i < 1000; i++) {
|
||||
m.set(i, 'foo');
|
||||
}
|
||||
|
||||
for (var i = 0; i < 1000; i++) {
|
||||
assertTrue(m.keys_.length <= 2 * m.getCount());
|
||||
m.remove(i);
|
||||
}
|
||||
assertTrue(m.isEmpty());
|
||||
assertEquals('', m.getKeys().join(''));
|
||||
}
|
||||
|
||||
|
||||
function testForEach() {
|
||||
var m = getMap();
|
||||
var s = '';
|
||||
goog.structs.forEach(m, function(val, key, m2) {
|
||||
assertNotUndefined(key);
|
||||
assertEquals(m, m2);
|
||||
s += key + val;
|
||||
});
|
||||
assertEquals(s, 'a0b1c2d3');
|
||||
}
|
||||
|
||||
function testFilter() {
|
||||
var m = getMap();
|
||||
|
||||
var m2 = goog.structs.filter(m, function(val, key, m3) {
|
||||
assertNotUndefined(key);
|
||||
assertEquals(m, m3);
|
||||
return val > 1;
|
||||
});
|
||||
assertEquals(stringifyMap(m2), 'c2d3');
|
||||
}
|
||||
|
||||
|
||||
function testMap() {
|
||||
var m = getMap();
|
||||
var m2 = goog.structs.map(m, function(val, key, m3) {
|
||||
assertNotUndefined(key);
|
||||
assertEquals(m, m3);
|
||||
return val * val;
|
||||
});
|
||||
assertEquals(stringifyMap(m2), 'a0b1c4d9');
|
||||
}
|
||||
|
||||
function testSome() {
|
||||
var m = getMap();
|
||||
var b = goog.structs.some(m, function(val, key, m2) {
|
||||
assertNotUndefined(key);
|
||||
assertEquals(m, m2);
|
||||
return val > 1;
|
||||
});
|
||||
assertTrue(b);
|
||||
var b = goog.structs.some(m, function(val, key, m2) {
|
||||
assertNotUndefined(key);
|
||||
assertEquals(m, m2);
|
||||
return val > 100;
|
||||
});
|
||||
assertFalse(b);
|
||||
}
|
||||
|
||||
function testEvery() {
|
||||
var m = getMap();
|
||||
var b = goog.structs.every(m, function(val, key, m2) {
|
||||
assertNotUndefined(key);
|
||||
assertEquals(m, m2);
|
||||
return val >= 0;
|
||||
});
|
||||
assertTrue(b);
|
||||
b = goog.structs.every(m, function(val, key, m2) {
|
||||
assertNotUndefined(key);
|
||||
assertEquals(m, m2);
|
||||
return val > 1;
|
||||
});
|
||||
assertFalse(b);
|
||||
}
|
||||
|
||||
function testContainsValue() {
|
||||
var m = getMap();
|
||||
assertTrue(m.containsValue(3));
|
||||
assertFalse(m.containsValue(4));
|
||||
}
|
||||
|
||||
function testObjectProperties() {
|
||||
var m = new goog.structs.Map;
|
||||
|
||||
assertEquals(m.get('toString'), undefined);
|
||||
assertEquals(m.get('valueOf'), undefined);
|
||||
assertEquals(m.get('eval'), undefined);
|
||||
assertEquals(m.get('toSource'), undefined);
|
||||
assertEquals(m.get('prototype'), undefined);
|
||||
assertEquals(m.get(':foo'), undefined);
|
||||
|
||||
m.set('toString', 'once');
|
||||
m.set('valueOf', 'upon');
|
||||
m.set('eval', 'a');
|
||||
m.set('toSource', 'midnight');
|
||||
m.set('prototype', 'dreary');
|
||||
m.set('hasOwnProperty', 'dark');
|
||||
m.set(':foo', 'happy');
|
||||
|
||||
assertEquals(m.get('toString'), 'once');
|
||||
assertEquals(m.get('valueOf'), 'upon');
|
||||
assertEquals(m.get('eval'), 'a');
|
||||
assertEquals(m.get('toSource'), 'midnight');
|
||||
assertEquals(m.get('prototype'), 'dreary');
|
||||
assertEquals(m.get('hasOwnProperty'), 'dark');
|
||||
assertEquals(m.get(':foo'), 'happy');
|
||||
|
||||
var keys = m.getKeys().join(',');
|
||||
assertEquals(keys,
|
||||
'toString,valueOf,eval,toSource,prototype,hasOwnProperty,:foo');
|
||||
|
||||
var values = m.getValues().join(',');
|
||||
assertEquals(values, 'once,upon,a,midnight,dreary,dark,happy');
|
||||
}
|
||||
|
||||
function testDuplicateKeys() {
|
||||
var m = new goog.structs.Map;
|
||||
|
||||
m.set('a', 1);
|
||||
m.set('b', 2);
|
||||
m.set('c', 3);
|
||||
m.set('d', 4);
|
||||
m.set('e', 5);
|
||||
m.set('f', 6);
|
||||
assertEquals(6, m.getKeys().length);
|
||||
m.set('foo', 1);
|
||||
assertEquals(7, m.getKeys().length);
|
||||
m.remove('foo');
|
||||
assertEquals(6, m.getKeys().length);
|
||||
m.set('foo', 2);
|
||||
assertEquals(7, m.getKeys().length);
|
||||
m.remove('foo');
|
||||
m.set('foo', 3);
|
||||
m.remove('foo');
|
||||
m.set('foo', 4);
|
||||
assertEquals(7, m.getKeys().length);
|
||||
}
|
||||
|
||||
function testGetKeyIterator() {
|
||||
var m = new goog.structs.Map;
|
||||
m.set('a', 1);
|
||||
m.set('b', 2);
|
||||
m.set('c', 3);
|
||||
m.set('d', 4);
|
||||
m.set('e', 5);
|
||||
|
||||
var iter = m.getKeyIterator();
|
||||
assertEquals('Should contain the keys', 'abcde', goog.iter.join(iter, ''));
|
||||
|
||||
m.remove('b');
|
||||
m.remove('d');
|
||||
iter = m.getKeyIterator();
|
||||
assertEquals('Should not contain the removed keys',
|
||||
'ace', goog.iter.join(iter, ''));
|
||||
}
|
||||
|
||||
function testGetValueIterator() {
|
||||
var m = new goog.structs.Map;
|
||||
m.set('a', 1);
|
||||
m.set('b', 2);
|
||||
m.set('c', 3);
|
||||
m.set('d', 4);
|
||||
m.set('e', 5);
|
||||
|
||||
var iter = m.getValueIterator();
|
||||
assertEquals('Should contain the values', '12345', goog.iter.join(iter, ''));
|
||||
|
||||
m.remove('b');
|
||||
m.remove('d');
|
||||
iter = m.getValueIterator();
|
||||
assertEquals('Should not contain the removed keys',
|
||||
'135', goog.iter.join(iter, ''));
|
||||
}
|
||||
|
||||
function testDefaultIterator() {
|
||||
// The default iterator should behave like the value iterator
|
||||
|
||||
var m = new goog.structs.Map;
|
||||
m.set('a', 1);
|
||||
m.set('b', 2);
|
||||
m.set('c', 3);
|
||||
m.set('d', 4);
|
||||
m.set('e', 5);
|
||||
|
||||
assertEquals('Should contain the values', '12345', goog.iter.join(m, ''));
|
||||
|
||||
m.remove('b');
|
||||
m.remove('d');
|
||||
assertEquals('Should not contain the removed keys',
|
||||
'135', goog.iter.join(m, ''));
|
||||
}
|
||||
|
||||
function testMutatedIterator() {
|
||||
var message = 'The map has changed since the iterator was created';
|
||||
|
||||
var m = new goog.structs.Map;
|
||||
m.set('a', 1);
|
||||
m.set('b', 2);
|
||||
m.set('c', 3);
|
||||
m.set('d', 4);
|
||||
|
||||
var iter = m.getValueIterator();
|
||||
m.set('e', 5);
|
||||
var ex = assertThrows('Expected an exception since the map has changed',
|
||||
function() {
|
||||
iter.next();
|
||||
});
|
||||
assertEquals(message, ex.message);
|
||||
|
||||
m = new goog.structs.Map;
|
||||
m.set('a', 1);
|
||||
m.set('b', 2);
|
||||
m.set('c', 3);
|
||||
m.set('d', 4);
|
||||
|
||||
iter = m.getValueIterator();
|
||||
m.remove('d');
|
||||
var ex = assertThrows('Expected an exception since the map has changed',
|
||||
function() {
|
||||
iter.next();
|
||||
});
|
||||
assertEquals(message, ex.message);
|
||||
|
||||
m = new goog.structs.Map;
|
||||
m.set('a', 1);
|
||||
m.set('b', 2);
|
||||
m.set('c', 3);
|
||||
m.set('d', 4);
|
||||
|
||||
iter = m.getValueIterator();
|
||||
m.set('d', 5);
|
||||
iter.next();
|
||||
// Changing an existing value is OK.
|
||||
iter.next();
|
||||
}
|
||||
|
||||
function testTranspose() {
|
||||
var m = new goog.structs.Map;
|
||||
m.set('a', 1);
|
||||
m.set('b', 2);
|
||||
m.set('c', 3);
|
||||
m.set('d', 4);
|
||||
m.set('e', 5);
|
||||
|
||||
var transposed = m.transpose();
|
||||
assertEquals('Should contain the keys', 'abcde',
|
||||
goog.iter.join(transposed, ''));
|
||||
}
|
||||
|
||||
function testToObject() {
|
||||
Object.prototype.b = 0;
|
||||
try {
|
||||
var map = new goog.structs.Map();
|
||||
map.set('a', 0);
|
||||
var obj = map.toObject();
|
||||
assertTrue('object representation has key "a"', obj.hasOwnProperty('a'));
|
||||
assertFalse('object representation does not have key "b"',
|
||||
obj.hasOwnProperty('b'));
|
||||
assertEquals('value for key "a"', 0, obj['a']);
|
||||
} finally {
|
||||
delete Object.prototype.b;
|
||||
}
|
||||
}
|
||||
|
||||
function testEqualsWithSameObject() {
|
||||
var map1 = getMap();
|
||||
assertTrue('maps are the same object', map1.equals(map1));
|
||||
}
|
||||
|
||||
function testEqualsWithDifferentSizeMaps() {
|
||||
var map1 = getMap();
|
||||
var map2 = new goog.structs.Map();
|
||||
|
||||
assertFalse('maps are different sizes', map1.equals(map2));
|
||||
}
|
||||
|
||||
function testEqualsWithDefaultEqualityFn() {
|
||||
var map1 = new goog.structs.Map();
|
||||
var map2 = new goog.structs.Map();
|
||||
|
||||
assertTrue('maps are both empty', map1.equals(map2));
|
||||
|
||||
map1 = getMap();
|
||||
map2 = getMap();
|
||||
assertTrue('maps are the same', map1.equals(map2));
|
||||
|
||||
map2.set('d', '3');
|
||||
assertFalse('maps have 3 and \'3\'', map1.equals(map2));
|
||||
}
|
||||
|
||||
function testEqualsWithCustomEqualityFn() {
|
||||
var map1 = new goog.structs.Map();
|
||||
var map2 = new goog.structs.Map();
|
||||
|
||||
map1.set('a', 0);
|
||||
map1.set('b', 1);
|
||||
|
||||
map2.set('a', '0');
|
||||
map2.set('b', '1');
|
||||
|
||||
var equalsFn = function(a, b) { return a == b };
|
||||
|
||||
assertTrue('maps are equal with ==', map1.equals(map2, equalsFn));
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
// Copyright 2006 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 Generic immutable node object to be used in collections.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
goog.provide('goog.structs.Node');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A generic immutable node. This can be used in various collections that
|
||||
* require a node object for its item (such as a heap).
|
||||
* @param {K} key Key.
|
||||
* @param {V} value Value.
|
||||
* @constructor
|
||||
* @template K, V
|
||||
*/
|
||||
goog.structs.Node = function(key, value) {
|
||||
/**
|
||||
* The key.
|
||||
* @private {K}
|
||||
*/
|
||||
this.key_ = key;
|
||||
|
||||
/**
|
||||
* The value.
|
||||
* @private {V}
|
||||
*/
|
||||
this.value_ = value;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the key.
|
||||
* @return {K} The key.
|
||||
*/
|
||||
goog.structs.Node.prototype.getKey = function() {
|
||||
return this.key_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the value.
|
||||
* @return {V} The value.
|
||||
*/
|
||||
goog.structs.Node.prototype.getValue = function() {
|
||||
return this.value_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Clones a node and returns a new node.
|
||||
* @return {!goog.structs.Node<K, V>} A new goog.structs.Node with the same
|
||||
* key value pair.
|
||||
*/
|
||||
goog.structs.Node.prototype.clone = function() {
|
||||
return new goog.structs.Node(this.key_, this.value_);
|
||||
};
|
||||
@@ -0,0 +1,376 @@
|
||||
// Copyright 2006 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: Pool.
|
||||
*
|
||||
*
|
||||
* A generic class for handling pools of objects.
|
||||
* When an object is released, it is attempted to be reused.
|
||||
*/
|
||||
|
||||
|
||||
goog.provide('goog.structs.Pool');
|
||||
|
||||
goog.require('goog.Disposable');
|
||||
goog.require('goog.structs.Queue');
|
||||
goog.require('goog.structs.Set');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A generic pool class. If min is greater than max, an error is thrown.
|
||||
* @param {number=} opt_minCount Min. number of objects (Default: 1).
|
||||
* @param {number=} opt_maxCount Max. number of objects (Default: 10).
|
||||
* @constructor
|
||||
* @extends {goog.Disposable}
|
||||
* @template T
|
||||
*/
|
||||
goog.structs.Pool = function(opt_minCount, opt_maxCount) {
|
||||
goog.Disposable.call(this);
|
||||
|
||||
/**
|
||||
* Minimum number of objects allowed
|
||||
* @private {number}
|
||||
*/
|
||||
this.minCount_ = opt_minCount || 0;
|
||||
|
||||
/**
|
||||
* Maximum number of objects allowed
|
||||
* @private {number}
|
||||
*/
|
||||
this.maxCount_ = opt_maxCount || 10;
|
||||
|
||||
// Make sure that the max and min constraints are valid.
|
||||
if (this.minCount_ > this.maxCount_) {
|
||||
throw Error(goog.structs.Pool.ERROR_MIN_MAX_);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set used to store objects that are currently in the pool and available
|
||||
* to be used.
|
||||
* @private {goog.structs.Queue<T>}
|
||||
*/
|
||||
this.freeQueue_ = new goog.structs.Queue();
|
||||
|
||||
/**
|
||||
* Set used to store objects that are currently in the pool and in use.
|
||||
* @private {goog.structs.Set<T>}
|
||||
*/
|
||||
this.inUseSet_ = new goog.structs.Set();
|
||||
|
||||
/**
|
||||
* The minimum delay between objects being made available, in milliseconds. If
|
||||
* this is 0, no minimum delay is enforced.
|
||||
* @protected {number}
|
||||
*/
|
||||
this.delay = 0;
|
||||
|
||||
/**
|
||||
* The time of the last object being made available, in milliseconds since the
|
||||
* epoch (i.e., the result of Date#toTime). If this is null, no access has
|
||||
* occurred yet.
|
||||
* @protected {number?}
|
||||
*/
|
||||
this.lastAccess = null;
|
||||
|
||||
// Make sure that the minCount constraint is satisfied.
|
||||
this.adjustForMinMax();
|
||||
|
||||
|
||||
// TODO(user): Remove once JSCompiler's undefined properties warnings
|
||||
// don't error for guarded properties.
|
||||
var magicProps = {canBeReused: 0};
|
||||
};
|
||||
goog.inherits(goog.structs.Pool, goog.Disposable);
|
||||
|
||||
|
||||
/**
|
||||
* Error to throw when the max/min constraint is attempted to be invalidated.
|
||||
* I.e., when it is attempted for maxCount to be less than minCount.
|
||||
* @type {string}
|
||||
* @private
|
||||
*/
|
||||
goog.structs.Pool.ERROR_MIN_MAX_ =
|
||||
'[goog.structs.Pool] Min can not be greater than max';
|
||||
|
||||
|
||||
/**
|
||||
* Error to throw when the Pool is attempted to be disposed and it is asked to
|
||||
* make sure that there are no objects that are in use (i.e., haven't been
|
||||
* released).
|
||||
* @type {string}
|
||||
* @private
|
||||
*/
|
||||
goog.structs.Pool.ERROR_DISPOSE_UNRELEASED_OBJS_ =
|
||||
'[goog.structs.Pool] Objects not released';
|
||||
|
||||
|
||||
/**
|
||||
* Sets the minimum count of the pool.
|
||||
* If min is greater than the max count of the pool, an error is thrown.
|
||||
* @param {number} min The minimum count of the pool.
|
||||
*/
|
||||
goog.structs.Pool.prototype.setMinimumCount = function(min) {
|
||||
// Check count constraints.
|
||||
if (min > this.maxCount_) {
|
||||
throw Error(goog.structs.Pool.ERROR_MIN_MAX_);
|
||||
}
|
||||
this.minCount_ = min;
|
||||
|
||||
// Adjust the objects in the pool as needed.
|
||||
this.adjustForMinMax();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sets the maximum count of the pool.
|
||||
* If max is less than the max count of the pool, an error is thrown.
|
||||
* @param {number} max The maximum count of the pool.
|
||||
*/
|
||||
goog.structs.Pool.prototype.setMaximumCount = function(max) {
|
||||
// Check count constraints.
|
||||
if (max < this.minCount_) {
|
||||
throw Error(goog.structs.Pool.ERROR_MIN_MAX_);
|
||||
}
|
||||
this.maxCount_ = max;
|
||||
|
||||
// Adjust the objects in the pool as needed.
|
||||
this.adjustForMinMax();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sets the minimum delay between objects being returned by getObject, in
|
||||
* milliseconds. This defaults to zero, meaning that no minimum delay is
|
||||
* enforced and objects may be used as soon as they're available.
|
||||
* @param {number} delay The minimum delay, in milliseconds.
|
||||
*/
|
||||
goog.structs.Pool.prototype.setDelay = function(delay) {
|
||||
this.delay = delay;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {T|undefined} A new object from the pool if there is one available,
|
||||
* otherwise undefined.
|
||||
*/
|
||||
goog.structs.Pool.prototype.getObject = function() {
|
||||
var time = goog.now();
|
||||
if (goog.isDefAndNotNull(this.lastAccess) &&
|
||||
time - this.lastAccess < this.delay) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
var obj = this.removeFreeObject_();
|
||||
if (obj) {
|
||||
this.lastAccess = time;
|
||||
this.inUseSet_.add(obj);
|
||||
}
|
||||
|
||||
return obj;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns an object to the pool of available objects so that it can be reused.
|
||||
* @param {T} obj The object to return to the pool of free objects.
|
||||
* @return {boolean} Whether the object was found in the Pool's set of in-use
|
||||
* objects (in other words, whether any action was taken).
|
||||
*/
|
||||
goog.structs.Pool.prototype.releaseObject = function(obj) {
|
||||
if (this.inUseSet_.remove(obj)) {
|
||||
this.addFreeObject(obj);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Removes a free object from the collection of objects that are free so that it
|
||||
* can be used.
|
||||
*
|
||||
* NOTE: This method does not mark the returned object as in use.
|
||||
*
|
||||
* @return {T|undefined} The object removed from the free collection, if there
|
||||
* is one available. Otherwise, undefined.
|
||||
* @private
|
||||
*/
|
||||
goog.structs.Pool.prototype.removeFreeObject_ = function() {
|
||||
var obj;
|
||||
while (this.getFreeCount() > 0) {
|
||||
obj = this.freeQueue_.dequeue();
|
||||
|
||||
if (!this.objectCanBeReused(obj)) {
|
||||
this.adjustForMinMax();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!obj && this.getCount() < this.maxCount_) {
|
||||
obj = this.createObject();
|
||||
}
|
||||
|
||||
return obj;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Adds an object to the collection of objects that are free. If the object can
|
||||
* not be added, then it is disposed.
|
||||
*
|
||||
* @param {T} obj The object to add to collection of free objects.
|
||||
*/
|
||||
goog.structs.Pool.prototype.addFreeObject = function(obj) {
|
||||
this.inUseSet_.remove(obj);
|
||||
if (this.objectCanBeReused(obj) && this.getCount() < this.maxCount_) {
|
||||
this.freeQueue_.enqueue(obj);
|
||||
} else {
|
||||
this.disposeObject(obj);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Adjusts the objects held in the pool to be within the min/max constraints.
|
||||
*
|
||||
* NOTE: It is possible that the number of objects in the pool will still be
|
||||
* greater than the maximum count of objects allowed. This will be the case
|
||||
* if no more free objects can be disposed of to get below the minimum count
|
||||
* (i.e., all objects are in use).
|
||||
*/
|
||||
goog.structs.Pool.prototype.adjustForMinMax = function() {
|
||||
var freeQueue = this.freeQueue_;
|
||||
|
||||
// Make sure the at least the minimum number of objects are created.
|
||||
while (this.getCount() < this.minCount_) {
|
||||
freeQueue.enqueue(this.createObject());
|
||||
}
|
||||
|
||||
// Make sure no more than the maximum number of objects are created.
|
||||
while (this.getCount() > this.maxCount_ && this.getFreeCount() > 0) {
|
||||
this.disposeObject(freeQueue.dequeue());
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Should be overridden by sub-classes to return an instance of the object type
|
||||
* that is expected in the pool.
|
||||
* @return {T} The created object.
|
||||
*/
|
||||
goog.structs.Pool.prototype.createObject = function() {
|
||||
return {};
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Should be overridden to dispose of an object. Default implementation is to
|
||||
* remove all its members, which should render it useless. Calls the object's
|
||||
* {@code dispose()} method, if available.
|
||||
* @param {T} obj The object to dispose.
|
||||
*/
|
||||
goog.structs.Pool.prototype.disposeObject = function(obj) {
|
||||
if (typeof obj.dispose == 'function') {
|
||||
obj.dispose();
|
||||
} else {
|
||||
for (var i in obj) {
|
||||
obj[i] = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Should be overridden to determine whether an object has become unusable and
|
||||
* should not be returned by getObject(). Calls the object's
|
||||
* {@code canBeReused()} method, if available.
|
||||
* @param {T} obj The object to test.
|
||||
* @return {boolean} Whether the object can be reused.
|
||||
*/
|
||||
goog.structs.Pool.prototype.objectCanBeReused = function(obj) {
|
||||
if (typeof obj.canBeReused == 'function') {
|
||||
return obj.canBeReused();
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns true if the given object is in the pool.
|
||||
* @param {T} obj The object to check the pool for.
|
||||
* @return {boolean} Whether the pool contains the object.
|
||||
*/
|
||||
goog.structs.Pool.prototype.contains = function(obj) {
|
||||
return this.freeQueue_.contains(obj) || this.inUseSet_.contains(obj);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the number of objects currently in the pool.
|
||||
* @return {number} Number of objects currently in the pool.
|
||||
*/
|
||||
goog.structs.Pool.prototype.getCount = function() {
|
||||
return this.freeQueue_.getCount() + this.inUseSet_.getCount();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the number of objects currently in use in the pool.
|
||||
* @return {number} Number of objects currently in use in the pool.
|
||||
*/
|
||||
goog.structs.Pool.prototype.getInUseCount = function() {
|
||||
return this.inUseSet_.getCount();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the number of objects currently free in the pool.
|
||||
* @return {number} Number of objects currently free in the pool.
|
||||
*/
|
||||
goog.structs.Pool.prototype.getFreeCount = function() {
|
||||
return this.freeQueue_.getCount();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Determines if the pool contains no objects.
|
||||
* @return {boolean} Whether the pool contains no objects.
|
||||
*/
|
||||
goog.structs.Pool.prototype.isEmpty = function() {
|
||||
return this.freeQueue_.isEmpty() && this.inUseSet_.isEmpty();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Disposes of the pool and all objects currently held in the pool.
|
||||
* @override
|
||||
* @protected
|
||||
*/
|
||||
goog.structs.Pool.prototype.disposeInternal = function() {
|
||||
goog.structs.Pool.superClass_.disposeInternal.call(this);
|
||||
if (this.getInUseCount() > 0) {
|
||||
throw Error(goog.structs.Pool.ERROR_DISPOSE_UNRELEASED_OBJS_);
|
||||
}
|
||||
delete this.inUseSet_;
|
||||
|
||||
// Call disposeObject on each object held by the pool.
|
||||
var freeQueue = this.freeQueue_;
|
||||
while (!freeQueue.isEmpty()) {
|
||||
this.disposeObject(freeQueue.dequeue());
|
||||
}
|
||||
delete this.freeQueue_;
|
||||
};
|
||||
@@ -0,0 +1,22 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
Copyright 2006 The Closure Library Authors. All Rights Reserved.
|
||||
|
||||
Use of this source code is governed by the Apache License, Version 2.0.
|
||||
See the COPYING file for details.
|
||||
-->
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<title>
|
||||
Closure Unit Tests - goog.structs.Pool
|
||||
</title>
|
||||
<script src="../base.js">
|
||||
</script>
|
||||
<script>
|
||||
goog.require('goog.structs.PoolTest');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,285 @@
|
||||
// Copyright 2006 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.
|
||||
|
||||
goog.provide('goog.structs.PoolTest');
|
||||
goog.setTestOnly('goog.structs.PoolTest');
|
||||
|
||||
goog.require('goog.structs.Pool');
|
||||
goog.require('goog.testing.MockClock');
|
||||
goog.require('goog.testing.jsunit');
|
||||
|
||||
// Implementation of the Pool class with isObjectDead() always returning TRUE,
|
||||
// so that the the Pool will not reuse any objects.
|
||||
function NoObjectReusePool(opt_min, opt_max) {
|
||||
goog.structs.Pool.call(this, opt_min, opt_max);
|
||||
}
|
||||
goog.inherits(NoObjectReusePool, goog.structs.Pool);
|
||||
|
||||
NoObjectReusePool.prototype.objectCanBeReused = function(obj) {
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
function testExceedMax1() {
|
||||
var p = new goog.structs.Pool(0, 3);
|
||||
var obj1 = p.getObject();
|
||||
var obj2 = p.getObject();
|
||||
var obj3 = p.getObject();
|
||||
var obj4 = p.getObject();
|
||||
var obj5 = p.getObject();
|
||||
|
||||
assertNotUndefined(obj1);
|
||||
assertNotUndefined(obj2);
|
||||
assertNotUndefined(obj3);
|
||||
assertUndefined(obj4);
|
||||
assertUndefined(obj5);
|
||||
}
|
||||
|
||||
|
||||
function testExceedMax2() {
|
||||
var p = new goog.structs.Pool(0, 1);
|
||||
var obj1 = p.getObject();
|
||||
var obj2 = p.getObject();
|
||||
var obj3 = p.getObject();
|
||||
var obj4 = p.getObject();
|
||||
var obj5 = p.getObject();
|
||||
|
||||
assertNotUndefined(obj1);
|
||||
assertUndefined(obj2);
|
||||
assertUndefined(obj3);
|
||||
assertUndefined(obj4);
|
||||
assertUndefined(obj5);
|
||||
}
|
||||
|
||||
|
||||
function testExceedMax3() {
|
||||
var p = new goog.structs.Pool(); // default: 10
|
||||
var objs = [];
|
||||
|
||||
for (var i = 0; i < 12; i++) {
|
||||
objs[i] = p.getObject();
|
||||
}
|
||||
|
||||
for (var i = 0; i < 10; i++) {
|
||||
assertNotNull('First 10 should be not null', objs[i]);
|
||||
}
|
||||
|
||||
assertUndefined(objs[10]);
|
||||
assertUndefined(objs[11]);
|
||||
}
|
||||
|
||||
|
||||
function testReleaseAndGet1() {
|
||||
var p = new goog.structs.Pool(0, 10);
|
||||
var o = p.getObject();
|
||||
assertEquals(1, p.getCount());
|
||||
assertEquals(1, p.getInUseCount());
|
||||
assertEquals(0, p.getFreeCount());
|
||||
assertTrue('Result should be true', p.releaseObject(o));
|
||||
assertEquals(1, p.getCount());
|
||||
assertEquals(0, p.getInUseCount());
|
||||
assertEquals(1, p.getFreeCount());
|
||||
}
|
||||
|
||||
|
||||
function testReleaseAndGet2() {
|
||||
var p = new NoObjectReusePool(0, 10);
|
||||
var o = p.getObject();
|
||||
assertEquals(1, p.getCount());
|
||||
assertEquals(1, p.getInUseCount());
|
||||
assertEquals(0, p.getFreeCount());
|
||||
assertTrue('Result should be true', p.releaseObject(o));
|
||||
assertEquals(0, p.getCount());
|
||||
assertEquals(0, p.getInUseCount());
|
||||
assertEquals(0, p.getFreeCount());
|
||||
}
|
||||
|
||||
|
||||
function testReleaseAndGet3() {
|
||||
var p = new goog.structs.Pool(0, 10);
|
||||
var o1 = p.getObject();
|
||||
var o2 = p.getObject();
|
||||
var o3 = p.getObject();
|
||||
var o4 = {};
|
||||
assertEquals(3, p.getCount());
|
||||
assertEquals(3, p.getInUseCount());
|
||||
assertEquals(0, p.getFreeCount());
|
||||
assertTrue('Result should be true', p.releaseObject(o1));
|
||||
assertTrue('Result should be true', p.releaseObject(o2));
|
||||
assertFalse('Result should be false', p.releaseObject(o4));
|
||||
assertEquals(3, p.getCount());
|
||||
assertEquals(1, p.getInUseCount());
|
||||
assertEquals(2, p.getFreeCount());
|
||||
}
|
||||
|
||||
|
||||
function testReleaseAndGet4() {
|
||||
var p = new NoObjectReusePool(0, 10);
|
||||
var o1 = p.getObject();
|
||||
var o2 = p.getObject();
|
||||
var o3 = p.getObject();
|
||||
var o4 = {};
|
||||
assertEquals(3, p.getCount());
|
||||
assertEquals(3, p.getInUseCount());
|
||||
assertEquals(0, p.getFreeCount());
|
||||
assertTrue('Result should be true', p.releaseObject(o1));
|
||||
assertTrue('Result should be true', p.releaseObject(o2));
|
||||
assertFalse('Result should be false', p.releaseObject(o4));
|
||||
assertEquals(1, p.getCount());
|
||||
assertEquals(1, p.getInUseCount());
|
||||
assertEquals(0, p.getFreeCount());
|
||||
}
|
||||
|
||||
|
||||
function testIsInPool1() {
|
||||
var p = new goog.structs.Pool();
|
||||
var o1 = p.getObject();
|
||||
var o2 = p.getObject();
|
||||
var o3 = p.getObject();
|
||||
var o4 = {};
|
||||
var o5 = {};
|
||||
var o6 = o1;
|
||||
|
||||
assertTrue(p.contains(o1));
|
||||
assertTrue(p.contains(o2));
|
||||
assertTrue(p.contains(o3));
|
||||
assertFalse(p.contains(o4));
|
||||
assertFalse(p.contains(o5));
|
||||
assertTrue(p.contains(o6));
|
||||
}
|
||||
|
||||
|
||||
function testSetMin1() {
|
||||
var p = new goog.structs.Pool(0, 10);
|
||||
|
||||
assertEquals(0, p.getCount());
|
||||
assertEquals(0, p.getInUseCount());
|
||||
assertEquals(0, p.getFreeCount());
|
||||
|
||||
p.setMinimumCount(10);
|
||||
|
||||
assertEquals(10, p.getCount());
|
||||
assertEquals(0, p.getInUseCount());
|
||||
assertEquals(10, p.getFreeCount());
|
||||
}
|
||||
|
||||
|
||||
function testSetMin2() {
|
||||
var p = new goog.structs.Pool(0, 10);
|
||||
|
||||
assertEquals(0, p.getCount());
|
||||
assertEquals(0, p.getInUseCount());
|
||||
assertEquals(0, p.getFreeCount());
|
||||
|
||||
var o1 = p.getObject();
|
||||
|
||||
assertEquals(1, p.getCount());
|
||||
assertEquals(1, p.getInUseCount());
|
||||
assertEquals(0, p.getFreeCount());
|
||||
|
||||
p.setMinimumCount(10);
|
||||
|
||||
assertEquals(10, p.getCount());
|
||||
assertEquals(1, p.getInUseCount());
|
||||
assertEquals(9, p.getFreeCount());
|
||||
}
|
||||
|
||||
|
||||
function testSetMax1() {
|
||||
var p = new goog.structs.Pool(0, 10);
|
||||
|
||||
assertEquals(0, p.getCount());
|
||||
assertEquals(0, p.getInUseCount());
|
||||
assertEquals(0, p.getFreeCount());
|
||||
|
||||
var o1 = p.getObject();
|
||||
var o2 = p.getObject();
|
||||
var o3 = p.getObject();
|
||||
var o4 = p.getObject();
|
||||
var o5 = p.getObject();
|
||||
|
||||
assertEquals(5, p.getCount());
|
||||
assertEquals(5, p.getInUseCount());
|
||||
assertEquals(0, p.getFreeCount());
|
||||
|
||||
assertTrue('Result should be true', p.releaseObject(o5));
|
||||
|
||||
assertEquals(5, p.getCount());
|
||||
assertEquals(4, p.getInUseCount());
|
||||
assertEquals(1, p.getFreeCount());
|
||||
|
||||
p.setMaximumCount(4);
|
||||
|
||||
assertEquals(4, p.getCount());
|
||||
assertEquals(4, p.getInUseCount());
|
||||
assertEquals(0, p.getFreeCount());
|
||||
}
|
||||
|
||||
|
||||
function testInvalidMinMax1() {
|
||||
var p = new goog.structs.Pool(0, 10);
|
||||
|
||||
assertEquals(0, p.getCount());
|
||||
assertEquals(0, p.getInUseCount());
|
||||
assertEquals(0, p.getFreeCount());
|
||||
|
||||
assertThrows(function() {
|
||||
p.setMinimumCount(11);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function testInvalidMinMax2() {
|
||||
var p = new goog.structs.Pool(5, 10);
|
||||
|
||||
assertEquals(5, p.getCount());
|
||||
assertEquals(0, p.getInUseCount());
|
||||
assertEquals(5, p.getFreeCount());
|
||||
|
||||
assertThrows(function() {
|
||||
p.setMaximumCount(4);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function testInvalidMinMax3() {
|
||||
assertThrows(function() {
|
||||
new goog.structs.Pool(10, 1);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function testRateLimiting() {
|
||||
var clock = new goog.testing.MockClock();
|
||||
clock.install();
|
||||
|
||||
var p = new goog.structs.Pool(0, 3);
|
||||
p.setDelay(100);
|
||||
|
||||
assertNotUndefined(p.getObject());
|
||||
assertUndefined(p.getObject());
|
||||
|
||||
clock.tick(100);
|
||||
assertNotUndefined(p.getObject());
|
||||
assertUndefined(p.getObject());
|
||||
|
||||
clock.tick(100);
|
||||
assertNotUndefined(p.getObject());
|
||||
assertUndefined(p.getObject());
|
||||
|
||||
clock.tick(100);
|
||||
assertUndefined(p.getObject());
|
||||
|
||||
goog.dispose(clock);
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
// Copyright 2006 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: Priority Pool.
|
||||
*
|
||||
*
|
||||
* An extending of Pool that handles queueing and prioritization.
|
||||
*/
|
||||
|
||||
|
||||
goog.provide('goog.structs.PriorityPool');
|
||||
|
||||
goog.require('goog.structs.Pool');
|
||||
goog.require('goog.structs.PriorityQueue');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A generic pool class. If max is greater than min, an error is thrown.
|
||||
* @param {number=} opt_minCount Min. number of objects (Default: 1).
|
||||
* @param {number=} opt_maxCount Max. number of objects (Default: 10).
|
||||
* @constructor
|
||||
* @extends {goog.structs.Pool<VALUE>}
|
||||
* @template VALUE
|
||||
*/
|
||||
goog.structs.PriorityPool = function(opt_minCount, opt_maxCount) {
|
||||
/**
|
||||
* The key for the most recent timeout created.
|
||||
* @private {number|undefined}
|
||||
*/
|
||||
this.delayTimeout_ = undefined;
|
||||
|
||||
/**
|
||||
* Queue of requests for pool objects.
|
||||
* @private {goog.structs.PriorityQueue<VALUE>}
|
||||
*/
|
||||
this.requestQueue_ = new goog.structs.PriorityQueue();
|
||||
|
||||
// Must break convention of putting the super-class's constructor first. This
|
||||
// is because the super-class constructor calls adjustForMinMax, which this
|
||||
// class overrides. In this class's implementation, it assumes that there
|
||||
// is a requestQueue_, and will error if not present.
|
||||
goog.structs.Pool.call(this, opt_minCount, opt_maxCount);
|
||||
};
|
||||
goog.inherits(goog.structs.PriorityPool, goog.structs.Pool);
|
||||
|
||||
|
||||
/**
|
||||
* Default priority for pool objects requests.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
goog.structs.PriorityPool.DEFAULT_PRIORITY_ = 100;
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.structs.PriorityPool.prototype.setDelay = function(delay) {
|
||||
goog.structs.PriorityPool.base(this, 'setDelay', delay);
|
||||
|
||||
// If the pool hasn't been accessed yet, no need to do anything.
|
||||
if (!goog.isDefAndNotNull(this.lastAccess)) {
|
||||
return;
|
||||
}
|
||||
|
||||
goog.global.clearTimeout(this.delayTimeout_);
|
||||
this.delayTimeout_ = goog.global.setTimeout(
|
||||
goog.bind(this.handleQueueRequests_, this),
|
||||
this.delay + this.lastAccess - goog.now());
|
||||
|
||||
// Handle all requests.
|
||||
this.handleQueueRequests_();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get a new object from the the pool, if there is one available, otherwise
|
||||
* return undefined.
|
||||
* @param {Function=} opt_callback The function to callback when an object is
|
||||
* available. This could be immediately. If this is not present, then an
|
||||
* object is immediately returned if available, or undefined if not.
|
||||
* @param {number=} opt_priority The priority of the request. A smaller value
|
||||
* means a higher priority.
|
||||
* @return {VALUE|undefined} The new object from the pool if there is one
|
||||
* available and a callback is not given. Otherwise, undefined.
|
||||
* @override
|
||||
*/
|
||||
goog.structs.PriorityPool.prototype.getObject = function(opt_callback,
|
||||
opt_priority) {
|
||||
if (!opt_callback) {
|
||||
var result = goog.structs.PriorityPool.base(this, 'getObject');
|
||||
if (result && this.delay) {
|
||||
this.delayTimeout_ = goog.global.setTimeout(
|
||||
goog.bind(this.handleQueueRequests_, this),
|
||||
this.delay);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
var priority = goog.isDef(opt_priority) ? opt_priority :
|
||||
goog.structs.PriorityPool.DEFAULT_PRIORITY_;
|
||||
this.requestQueue_.enqueue(priority, opt_callback);
|
||||
|
||||
// Handle all requests.
|
||||
this.handleQueueRequests_();
|
||||
|
||||
return undefined;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handles the request queue. Tries to fires off as many queued requests as
|
||||
* possible.
|
||||
* @private
|
||||
*/
|
||||
goog.structs.PriorityPool.prototype.handleQueueRequests_ = function() {
|
||||
var requestQueue = this.requestQueue_;
|
||||
while (requestQueue.getCount() > 0) {
|
||||
var obj = this.getObject();
|
||||
|
||||
if (!obj) {
|
||||
return;
|
||||
} else {
|
||||
var requestCallback = requestQueue.dequeue();
|
||||
requestCallback.apply(this, [obj]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Adds an object to the collection of objects that are free. If the object can
|
||||
* not be added, then it is disposed.
|
||||
*
|
||||
* NOTE: This method does not remove the object from the in use collection.
|
||||
*
|
||||
* @param {VALUE} obj The object to add to the collection of free objects.
|
||||
* @override
|
||||
*/
|
||||
goog.structs.PriorityPool.prototype.addFreeObject = function(obj) {
|
||||
goog.structs.PriorityPool.superClass_.addFreeObject.call(this, obj);
|
||||
|
||||
// Handle all requests.
|
||||
this.handleQueueRequests_();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Adjusts the objects held in the pool to be within the min/max constraints.
|
||||
*
|
||||
* NOTE: It is possible that the number of objects in the pool will still be
|
||||
* greater than the maximum count of objects allowed. This will be the case
|
||||
* if no more free objects can be disposed of to get below the minimum count
|
||||
* (i.e., all objects are in use).
|
||||
* @override
|
||||
*/
|
||||
goog.structs.PriorityPool.prototype.adjustForMinMax = function() {
|
||||
goog.structs.PriorityPool.superClass_.adjustForMinMax.call(this);
|
||||
|
||||
// Handle all requests.
|
||||
this.handleQueueRequests_();
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.structs.PriorityPool.prototype.disposeInternal = function() {
|
||||
goog.structs.PriorityPool.superClass_.disposeInternal.call(this);
|
||||
goog.global.clearTimeout(this.delayTimeout_);
|
||||
this.requestQueue_.clear();
|
||||
this.requestQueue_ = null;
|
||||
};
|
||||
@@ -0,0 +1,22 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
Copyright 2006 The Closure Library Authors. All Rights Reserved.
|
||||
|
||||
Use of this source code is governed by the Apache License, Version 2.0.
|
||||
See the COPYING file for details.
|
||||
-->
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<title>
|
||||
Closure Unit Tests - goog.structs.PriorityPool
|
||||
</title>
|
||||
<script src="../base.js">
|
||||
</script>
|
||||
<script>
|
||||
goog.require('goog.structs.PriorityPoolTest');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,582 @@
|
||||
// Copyright 2006 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.
|
||||
|
||||
goog.provide('goog.structs.PriorityPoolTest');
|
||||
goog.setTestOnly('goog.structs.PriorityPoolTest');
|
||||
|
||||
goog.require('goog.structs.PriorityPool');
|
||||
goog.require('goog.testing.MockClock');
|
||||
goog.require('goog.testing.jsunit');
|
||||
|
||||
// Implementation of the Pool class with isObjectDead() always returning TRUE,
|
||||
// so that the the Pool will not reuse any objects.
|
||||
function NoObjectReusePriorityPool(opt_min, opt_max) {
|
||||
goog.structs.PriorityPool.call(this, opt_min, opt_max);
|
||||
}
|
||||
goog.inherits(NoObjectReusePriorityPool, goog.structs.PriorityPool);
|
||||
|
||||
NoObjectReusePriorityPool.prototype.objectCanBeReused = function(obj) {
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
function testExceedMax1() {
|
||||
var p = new goog.structs.PriorityPool(0, 3);
|
||||
|
||||
var getCount1 = 0;
|
||||
var callback1 = function(obj) {
|
||||
assertNotNull(obj);
|
||||
getCount1++;
|
||||
};
|
||||
|
||||
var getCount2 = 0;
|
||||
var callback2 = function(obj) {
|
||||
getCount2++;
|
||||
};
|
||||
|
||||
p.getObject(callback1);
|
||||
p.getObject(callback1);
|
||||
p.getObject(callback1);
|
||||
p.getObject(callback2);
|
||||
p.getObject(callback2);
|
||||
p.getObject(callback2);
|
||||
|
||||
assertEquals('getCount for allocated, Should be 3', getCount1, 3);
|
||||
assertEquals('getCount for unallocated, Should be 0', getCount2, 0);
|
||||
}
|
||||
|
||||
|
||||
function testExceedMax2() {
|
||||
var p = new goog.structs.PriorityPool(0, 1);
|
||||
|
||||
var getCount1 = 0;
|
||||
var callback1 = function(obj) {
|
||||
assertNotNull(obj);
|
||||
getCount1++;
|
||||
};
|
||||
|
||||
var getCount2 = 0;
|
||||
var callback2 = function(obj) {
|
||||
getCount2++;
|
||||
};
|
||||
|
||||
p.getObject(callback1);
|
||||
p.getObject(callback2);
|
||||
p.getObject(callback2);
|
||||
p.getObject(callback2);
|
||||
p.getObject(callback2);
|
||||
p.getObject(callback2);
|
||||
|
||||
assertEquals('getCount for allocated, Should be 1', getCount1, 1);
|
||||
assertEquals('getCount for unallocated, Should be 0', getCount2, 0);
|
||||
}
|
||||
|
||||
function testExceedMax3() {
|
||||
var p = new goog.structs.PriorityPool(0, 2);
|
||||
|
||||
var obj1 = null;
|
||||
var callback1 = function(obj) {
|
||||
obj1 = obj;
|
||||
};
|
||||
|
||||
var obj2 = null;
|
||||
var callback2 = function(obj) {
|
||||
obj2 = obj;
|
||||
};
|
||||
|
||||
var obj3 = null;
|
||||
var callback3 = function(obj) {
|
||||
obj3 = obj;
|
||||
};
|
||||
|
||||
p.getObject(callback1);
|
||||
p.getObject(callback2);
|
||||
p.getObject(callback3);
|
||||
|
||||
assertNotNull(obj1);
|
||||
assertNotNull(obj2);
|
||||
assertNull(obj3);
|
||||
}
|
||||
|
||||
function testExceedMax4() {
|
||||
var p = new goog.structs.PriorityPool(); // default: 10
|
||||
var objs = [];
|
||||
|
||||
var getCount1 = 0;
|
||||
var callback1 = function(obj) {
|
||||
assertNotNull(obj);
|
||||
getCount1++;
|
||||
};
|
||||
|
||||
var getCount2 = 0;
|
||||
var callback2 = function(obj) {
|
||||
getCount2++;
|
||||
};
|
||||
|
||||
for (var i = 0; i < 12; i++) {
|
||||
p.getObject(i < 10 ? callback1 : callback2);
|
||||
}
|
||||
|
||||
assertEquals('getCount for allocated, Should be 10', getCount1, 10);
|
||||
assertEquals('getCount for unallocated, Should be 0', getCount2, 0);
|
||||
}
|
||||
|
||||
|
||||
function testReleaseAndGet1() {
|
||||
var p = new goog.structs.PriorityPool(0, 10);
|
||||
|
||||
var o = null;
|
||||
var callback = function(obj) {
|
||||
o = obj;
|
||||
};
|
||||
|
||||
p.getObject(callback);
|
||||
assertEquals(1, p.getCount());
|
||||
assertEquals(1, p.getInUseCount());
|
||||
assertEquals(0, p.getFreeCount());
|
||||
assertTrue('Result should be true', p.releaseObject(o));
|
||||
assertEquals(1, p.getCount());
|
||||
assertEquals(0, p.getInUseCount());
|
||||
assertEquals(1, p.getFreeCount());
|
||||
}
|
||||
|
||||
|
||||
function testReleaseAndGet2() {
|
||||
var p = new NoObjectReusePriorityPool(0, 10);
|
||||
|
||||
var o = null;
|
||||
var callback = function(obj) {
|
||||
o = obj;
|
||||
};
|
||||
|
||||
p.getObject(callback);
|
||||
assertEquals(1, p.getCount());
|
||||
assertEquals(1, p.getInUseCount());
|
||||
assertEquals(0, p.getFreeCount());
|
||||
assertTrue('Result should be true', p.releaseObject(o));
|
||||
assertEquals(0, p.getCount());
|
||||
assertEquals(0, p.getInUseCount());
|
||||
assertEquals(0, p.getFreeCount());
|
||||
}
|
||||
|
||||
|
||||
function testReleaseAndGet3() {
|
||||
var p = new goog.structs.PriorityPool(0, 10);
|
||||
var o1 = null;
|
||||
var callback1 = function(obj) {
|
||||
o1 = obj;
|
||||
};
|
||||
|
||||
var o2 = null;
|
||||
var callback2 = function(obj) {
|
||||
o2 = obj;
|
||||
};
|
||||
|
||||
var o3 = null;
|
||||
var callback3 = function(obj) {
|
||||
o3 = obj;
|
||||
};
|
||||
|
||||
var o4 = {};
|
||||
|
||||
p.getObject(callback1);
|
||||
p.getObject(callback2);
|
||||
p.getObject(callback3);
|
||||
|
||||
assertEquals(3, p.getCount());
|
||||
assertEquals(3, p.getInUseCount());
|
||||
assertEquals(0, p.getFreeCount());
|
||||
assertTrue('Result should be true', p.releaseObject(o1));
|
||||
assertTrue('Result should be true', p.releaseObject(o2));
|
||||
assertFalse('Result should be false', p.releaseObject(o4));
|
||||
assertEquals(3, p.getCount());
|
||||
assertEquals(1, p.getInUseCount());
|
||||
assertEquals(2, p.getFreeCount());
|
||||
}
|
||||
|
||||
|
||||
function testReleaseAndGet4() {
|
||||
var p = new NoObjectReusePriorityPool(0, 10);
|
||||
|
||||
var o1 = null;
|
||||
var callback1 = function(obj) {
|
||||
o1 = obj;
|
||||
};
|
||||
|
||||
var o2 = null;
|
||||
var callback2 = function(obj) {
|
||||
o2 = obj;
|
||||
};
|
||||
|
||||
var o3 = null;
|
||||
var callback3 = function(obj) {
|
||||
o3 = obj;
|
||||
};
|
||||
|
||||
var o4 = {};
|
||||
|
||||
p.getObject(callback1);
|
||||
p.getObject(callback2);
|
||||
p.getObject(callback3);
|
||||
assertEquals(3, p.getCount());
|
||||
assertEquals(3, p.getInUseCount());
|
||||
assertEquals(0, p.getFreeCount());
|
||||
assertTrue('Result should be true', p.releaseObject(o1));
|
||||
assertTrue('Result should be true', p.releaseObject(o2));
|
||||
assertFalse('Result should be false', p.releaseObject(o4));
|
||||
assertEquals(1, p.getCount());
|
||||
assertEquals(1, p.getInUseCount());
|
||||
assertEquals(0, p.getFreeCount());
|
||||
}
|
||||
|
||||
|
||||
function testIsInPool1() {
|
||||
var p = new goog.structs.PriorityPool();
|
||||
var o1 = null;
|
||||
var callback1 = function(obj) {
|
||||
o1 = obj;
|
||||
};
|
||||
|
||||
var o2 = null;
|
||||
var callback2 = function(obj) {
|
||||
o2 = obj;
|
||||
};
|
||||
|
||||
var o3 = null;
|
||||
var callback3 = function(obj) {
|
||||
o3 = obj;
|
||||
};
|
||||
|
||||
var o4 = {};
|
||||
var o5 = {};
|
||||
|
||||
p.getObject(callback1);
|
||||
p.getObject(callback2);
|
||||
p.getObject(callback3);
|
||||
var o6 = o1;
|
||||
|
||||
assertTrue(p.contains(o1));
|
||||
assertTrue(p.contains(o2));
|
||||
assertTrue(p.contains(o3));
|
||||
assertFalse(p.contains(o4));
|
||||
assertFalse(p.contains(o5));
|
||||
assertTrue(p.contains(o6));
|
||||
}
|
||||
|
||||
|
||||
function testSetMin1() {
|
||||
var p = new goog.structs.PriorityPool(0, 10);
|
||||
|
||||
assertEquals(0, p.getCount());
|
||||
assertEquals(0, p.getInUseCount());
|
||||
assertEquals(0, p.getFreeCount());
|
||||
|
||||
p.setMinimumCount(10);
|
||||
|
||||
assertEquals(10, p.getCount());
|
||||
assertEquals(0, p.getInUseCount());
|
||||
assertEquals(10, p.getFreeCount());
|
||||
}
|
||||
|
||||
|
||||
function testSetMin2() {
|
||||
var p = new goog.structs.PriorityPool(0, 10);
|
||||
|
||||
assertEquals(0, p.getCount());
|
||||
assertEquals(0, p.getInUseCount());
|
||||
assertEquals(0, p.getFreeCount());
|
||||
|
||||
var o1 = null;
|
||||
var callback1 = function(obj) {
|
||||
o1 = obj;
|
||||
};
|
||||
p.getObject(callback1);
|
||||
|
||||
assertEquals(1, p.getCount());
|
||||
assertEquals(1, p.getInUseCount());
|
||||
assertEquals(0, p.getFreeCount());
|
||||
|
||||
p.setMinimumCount(10);
|
||||
|
||||
assertEquals(10, p.getCount());
|
||||
assertEquals(1, p.getInUseCount());
|
||||
assertEquals(9, p.getFreeCount());
|
||||
}
|
||||
|
||||
|
||||
function testSetMax1() {
|
||||
var p = new goog.structs.PriorityPool(0, 10);
|
||||
|
||||
assertEquals(0, p.getCount());
|
||||
assertEquals(0, p.getInUseCount());
|
||||
assertEquals(0, p.getFreeCount());
|
||||
|
||||
var o1 = null;
|
||||
var callback1 = function(obj) {
|
||||
o1 = obj;
|
||||
};
|
||||
|
||||
var o2 = null;
|
||||
var callback2 = function(obj) {
|
||||
o2 = obj;
|
||||
};
|
||||
|
||||
var o3 = null;
|
||||
var callback3 = function(obj) {
|
||||
o3 = obj;
|
||||
};
|
||||
|
||||
var o4 = null;
|
||||
var callback4 = function(obj) {
|
||||
o4 = obj;
|
||||
};
|
||||
|
||||
var o5 = null;
|
||||
var callback5 = function(obj) {
|
||||
o5 = obj;
|
||||
};
|
||||
|
||||
p.getObject(callback1);
|
||||
p.getObject(callback2);
|
||||
p.getObject(callback3);
|
||||
p.getObject(callback4);
|
||||
p.getObject(callback5);
|
||||
|
||||
assertEquals(5, p.getCount());
|
||||
assertEquals(5, p.getInUseCount());
|
||||
assertEquals(0, p.getFreeCount());
|
||||
|
||||
assertTrue('Result should be true', p.releaseObject(o5));
|
||||
|
||||
assertEquals(5, p.getCount());
|
||||
assertEquals(4, p.getInUseCount());
|
||||
assertEquals(1, p.getFreeCount());
|
||||
|
||||
p.setMaximumCount(4);
|
||||
|
||||
assertEquals(4, p.getCount());
|
||||
assertEquals(4, p.getInUseCount());
|
||||
assertEquals(0, p.getFreeCount());
|
||||
}
|
||||
|
||||
|
||||
function testInvalidMinMax1() {
|
||||
var p = new goog.structs.PriorityPool(0, 10);
|
||||
|
||||
assertEquals(0, p.getCount());
|
||||
assertEquals(0, p.getInUseCount());
|
||||
assertEquals(0, p.getFreeCount());
|
||||
|
||||
assertThrows(function() {
|
||||
p.setMinimumCount(11);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function testInvalidMinMax2() {
|
||||
var p = new goog.structs.PriorityPool(5, 10);
|
||||
|
||||
assertEquals(5, p.getCount());
|
||||
assertEquals(0, p.getInUseCount());
|
||||
assertEquals(5, p.getFreeCount());
|
||||
|
||||
assertThrows(function() {
|
||||
p.setMaximumCount(4);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function testInvalidMinMax3() {
|
||||
assertThrows(function() {
|
||||
new goog.structs.PriorityPool(10, 1);
|
||||
});
|
||||
}
|
||||
|
||||
function testQueue1() {
|
||||
var p = new goog.structs.PriorityPool(0, 2);
|
||||
|
||||
var o1 = null;
|
||||
var callback1 = function(obj) {
|
||||
o1 = obj;
|
||||
};
|
||||
|
||||
var o2 = null;
|
||||
var callback2 = function(obj) {
|
||||
o2 = obj;
|
||||
};
|
||||
|
||||
var o3 = null;
|
||||
var callback3 = function(obj) {
|
||||
o3 = obj;
|
||||
};
|
||||
|
||||
p.getObject(callback1);
|
||||
p.getObject(callback2);
|
||||
p.getObject(callback3);
|
||||
|
||||
assertNotNull(o1);
|
||||
assertNotNull(o2);
|
||||
assertNull(o3);
|
||||
|
||||
p.releaseObject(o1);
|
||||
assertNotNull(o3);
|
||||
}
|
||||
|
||||
|
||||
function testPriority1() {
|
||||
var p = new goog.structs.PriorityPool(0, 2);
|
||||
|
||||
var o1 = null;
|
||||
var callback1 = function(obj) {
|
||||
o1 = obj;
|
||||
};
|
||||
|
||||
var o2 = null;
|
||||
var callback2 = function(obj) {
|
||||
o2 = obj;
|
||||
};
|
||||
|
||||
var o3 = null;
|
||||
var callback3 = function(obj) {
|
||||
o3 = obj;
|
||||
};
|
||||
|
||||
var o4 = null;
|
||||
var callback4 = function(obj) {
|
||||
o4 = obj;
|
||||
};
|
||||
|
||||
var o5 = null;
|
||||
var callback5 = function(obj) {
|
||||
o5 = obj;
|
||||
};
|
||||
|
||||
var o6 = null;
|
||||
var callback6 = function(obj) {
|
||||
o6 = obj;
|
||||
};
|
||||
|
||||
p.getObject(callback1); // Initially seeded requests.
|
||||
p.getObject(callback2);
|
||||
|
||||
p.getObject(callback3, 101); // Lowest priority.
|
||||
p.getObject(callback4); // Second lowest priority (default is 100).
|
||||
p.getObject(callback5, 99); // Second highest priority.
|
||||
p.getObject(callback6, 0); // Highest priority.
|
||||
|
||||
assertNotNull(o1);
|
||||
assertNotNull(o2);
|
||||
assertNull(o3);
|
||||
assertNull(o4);
|
||||
assertNull(o5);
|
||||
assertNull(o6);
|
||||
|
||||
p.releaseObject(o1); // Release the first initially seeded request (o1).
|
||||
assertNotNull(o6); // Make sure the highest priority request (o6) started.
|
||||
assertNull(o3);
|
||||
assertNull(o4);
|
||||
assertNull(o5);
|
||||
|
||||
p.releaseObject(o2); // Release the second, initially seeded request (o2).
|
||||
assertNotNull(o5); // The second highest priority request starts (o5).
|
||||
assertNull(o3);
|
||||
assertNull(o4);
|
||||
|
||||
p.releaseObject(o6);
|
||||
assertNotNull(o4);
|
||||
assertNull(o3);
|
||||
}
|
||||
|
||||
|
||||
function testRateLimiting() {
|
||||
var clock = new goog.testing.MockClock();
|
||||
clock.install();
|
||||
|
||||
var p = new goog.structs.PriorityPool(0, 4);
|
||||
p.setDelay(100);
|
||||
|
||||
var getCount = 0;
|
||||
var callback = function(obj) {
|
||||
assertNotNull(obj);
|
||||
getCount++;
|
||||
};
|
||||
|
||||
p.getObject(callback);
|
||||
assertEquals(1, getCount);
|
||||
|
||||
p.getObject(callback);
|
||||
assertEquals(1, getCount);
|
||||
|
||||
clock.tick(100);
|
||||
assertEquals(2, getCount);
|
||||
|
||||
p.getObject(callback);
|
||||
p.getObject(callback);
|
||||
assertEquals(2, getCount);
|
||||
|
||||
clock.tick(100);
|
||||
assertEquals(3, getCount);
|
||||
|
||||
clock.tick(100);
|
||||
assertEquals(4, getCount);
|
||||
|
||||
p.getObject(callback);
|
||||
assertEquals(4, getCount);
|
||||
|
||||
clock.tick(100);
|
||||
assertEquals(4, getCount);
|
||||
|
||||
goog.dispose(clock);
|
||||
}
|
||||
|
||||
|
||||
function testRateLimitingWithChangingDelay() {
|
||||
var clock = new goog.testing.MockClock();
|
||||
clock.install();
|
||||
|
||||
var p = new goog.structs.PriorityPool(0, 3);
|
||||
p.setDelay(100);
|
||||
|
||||
var getCount = 0;
|
||||
var callback = function(obj) {
|
||||
assertNotNull(obj);
|
||||
getCount++;
|
||||
};
|
||||
|
||||
p.getObject(callback);
|
||||
assertEquals(1, getCount);
|
||||
|
||||
p.getObject(callback);
|
||||
assertEquals(1, getCount);
|
||||
|
||||
clock.tick(50);
|
||||
assertEquals(1, getCount);
|
||||
|
||||
p.setDelay(50);
|
||||
assertEquals(2, getCount);
|
||||
|
||||
p.getObject(callback);
|
||||
assertEquals(2, getCount);
|
||||
|
||||
clock.tick(20);
|
||||
assertEquals(2, getCount);
|
||||
|
||||
p.setDelay(40);
|
||||
assertEquals(2, getCount);
|
||||
|
||||
clock.tick(20);
|
||||
assertEquals(3, getCount);
|
||||
|
||||
goog.dispose(clock);
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
// Copyright 2006 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: Priority Queue.
|
||||
*
|
||||
*
|
||||
* This file provides the implementation of a Priority Queue. Smaller priorities
|
||||
* move to the front of the queue. If two values have the same priority,
|
||||
* it is arbitrary which value will come to the front of the queue first.
|
||||
*/
|
||||
|
||||
// TODO(user): Should this rely on natural ordering via some Comparable
|
||||
// interface?
|
||||
|
||||
|
||||
goog.provide('goog.structs.PriorityQueue');
|
||||
|
||||
goog.require('goog.structs.Heap');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Class for Priority Queue datastructure.
|
||||
*
|
||||
* @constructor
|
||||
* @extends {goog.structs.Heap<number, VALUE>}
|
||||
* @template VALUE
|
||||
* @final
|
||||
*/
|
||||
goog.structs.PriorityQueue = function() {
|
||||
goog.structs.Heap.call(this);
|
||||
};
|
||||
goog.inherits(goog.structs.PriorityQueue, goog.structs.Heap);
|
||||
|
||||
|
||||
/**
|
||||
* Puts the specified value in the queue.
|
||||
* @param {number} priority The priority of the value. A smaller value here
|
||||
* means a higher priority.
|
||||
* @param {VALUE} value The value.
|
||||
*/
|
||||
goog.structs.PriorityQueue.prototype.enqueue = function(priority, value) {
|
||||
this.insert(priority, value);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Retrieves and removes the head of this queue.
|
||||
* @return {VALUE} The element at the head of this queue. Returns undefined if
|
||||
* the queue is empty.
|
||||
*/
|
||||
goog.structs.PriorityQueue.prototype.dequeue = function() {
|
||||
return this.remove();
|
||||
};
|
||||
@@ -0,0 +1,22 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
Copyright 2006 The Closure Library Authors. All Rights Reserved.
|
||||
|
||||
Use of this source code is governed by the Apache License, Version 2.0.
|
||||
See the COPYING file for details.
|
||||
-->
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<title>
|
||||
Closure Unit Tests - goog.structs.PriorityQueue
|
||||
</title>
|
||||
<script src="../base.js">
|
||||
</script>
|
||||
<script>
|
||||
goog.require('goog.structs.PriorityQueueTest');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,175 @@
|
||||
// Copyright 2006 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.
|
||||
|
||||
goog.provide('goog.structs.PriorityQueueTest');
|
||||
goog.setTestOnly('goog.structs.PriorityQueueTest');
|
||||
|
||||
goog.require('goog.structs');
|
||||
goog.require('goog.structs.PriorityQueue');
|
||||
goog.require('goog.testing.jsunit');
|
||||
|
||||
function getPriorityQueue() {
|
||||
var p = new goog.structs.PriorityQueue();
|
||||
p.enqueue(0, 'a');
|
||||
p.enqueue(1, 'b');
|
||||
p.enqueue(2, 'c');
|
||||
p.enqueue(3, 'd');
|
||||
return p;
|
||||
}
|
||||
|
||||
|
||||
function getPriorityQueue2() {
|
||||
var p = new goog.structs.PriorityQueue();
|
||||
p.insert(1, 'b');
|
||||
p.insert(3, 'd');
|
||||
p.insert(0, 'a');
|
||||
p.insert(2, 'c');
|
||||
return p;
|
||||
}
|
||||
|
||||
|
||||
function testGetCount1() {
|
||||
var p = getPriorityQueue();
|
||||
assertEquals('count, should be 4', p.getCount(), 4);
|
||||
p.dequeue();
|
||||
assertEquals('count, should be 3', p.getCount(), 3);
|
||||
}
|
||||
|
||||
|
||||
function testGetCount2() {
|
||||
var p = getPriorityQueue();
|
||||
assertEquals('count, should be 4', p.getCount(), 4);
|
||||
p.dequeue();
|
||||
assertEquals('count, should be 3', p.getCount(), 3);
|
||||
}
|
||||
|
||||
|
||||
function testGetCount3() {
|
||||
var p = getPriorityQueue();
|
||||
p.dequeue();
|
||||
p.dequeue();
|
||||
p.dequeue();
|
||||
p.dequeue();
|
||||
assertEquals('count, should be 0', p.getCount(), 0);
|
||||
}
|
||||
|
||||
|
||||
function testKeys() {
|
||||
var p = getPriorityQueue();
|
||||
var keys = p.getKeys();
|
||||
for (var i = 0; i < 4; i++) {
|
||||
assertTrue('getKeys, key ' + i + ' found', goog.structs.contains(keys, i));
|
||||
}
|
||||
assertEquals('getKeys, Should be 4 keys', goog.structs.getCount(keys), 4);
|
||||
}
|
||||
|
||||
|
||||
function testValues() {
|
||||
var p = getPriorityQueue();
|
||||
var values = p.getValues();
|
||||
|
||||
assertTrue('getKeys, value "a" found', goog.structs.contains(values, 'a'));
|
||||
assertTrue('getKeys, value "b" found', goog.structs.contains(values, 'b'));
|
||||
assertTrue('getKeys, value "c" found', goog.structs.contains(values, 'c'));
|
||||
assertTrue('getKeys, value "d" found', goog.structs.contains(values, 'd'));
|
||||
assertEquals('getKeys, Should be 4 keys', goog.structs.getCount(values), 4);
|
||||
}
|
||||
|
||||
|
||||
function testClear() {
|
||||
var p = getPriorityQueue();
|
||||
p.clear();
|
||||
assertTrue('cleared so it should be empty', p.isEmpty());
|
||||
}
|
||||
|
||||
|
||||
function testIsEmpty() {
|
||||
var p = getPriorityQueue();
|
||||
assertFalse('4 values so should not be empty', p.isEmpty());
|
||||
|
||||
p.dequeue();
|
||||
p.dequeue();
|
||||
p.dequeue();
|
||||
assertFalse('1 values so should not be empty', p.isEmpty());
|
||||
|
||||
p.dequeue();
|
||||
assertTrue('0 values so should be empty', p.isEmpty());
|
||||
}
|
||||
|
||||
|
||||
function testPeek1() {
|
||||
var p = getPriorityQueue();
|
||||
assertEquals('peek, Should be "a"', p.peek(), 'a');
|
||||
}
|
||||
|
||||
|
||||
function testPeek2() {
|
||||
var p = getPriorityQueue2();
|
||||
assertEquals('peek, Should be "a"', p.peek(), 'a');
|
||||
}
|
||||
|
||||
|
||||
function testPeek3() {
|
||||
var p = getPriorityQueue();
|
||||
p.clear();
|
||||
assertEquals('peek, Should be "a"', p.peek(), undefined);
|
||||
}
|
||||
|
||||
|
||||
function testDequeue1() {
|
||||
var p = getPriorityQueue();
|
||||
|
||||
assertEquals('dequeue, Should be "a"', p.dequeue(), 'a');
|
||||
assertEquals('dequeue, Should be "b"', p.dequeue(), 'b');
|
||||
assertEquals('dequeue, Should be "c"', p.dequeue(), 'c');
|
||||
assertEquals('dequeue, Should be "d"', p.dequeue(), 'd');
|
||||
}
|
||||
|
||||
|
||||
function testDequeue2() {
|
||||
var p = getPriorityQueue2();
|
||||
|
||||
assertEquals('dequeue, Should be "a"', p.dequeue(), 'a');
|
||||
assertEquals('dequeue, Should be "b"', p.dequeue(), 'b');
|
||||
assertEquals('dequeue, Should be "c"', p.dequeue(), 'c');
|
||||
assertEquals('dequeue, Should be "d"', p.dequeue(), 'd');
|
||||
}
|
||||
|
||||
|
||||
function testEnqueuePeek1() {
|
||||
var p = new goog.structs.PriorityQueue();
|
||||
|
||||
p.enqueue(3, 'd');
|
||||
assertEquals('peak, Should be "d"', p.peek(), 'd');
|
||||
p.enqueue(2, 'c');
|
||||
assertEquals('peak, Should be "c"', p.peek(), 'c');
|
||||
p.enqueue(1, 'b');
|
||||
assertEquals('peak, Should be "b"', p.peek(), 'b');
|
||||
p.enqueue(0, 'a');
|
||||
assertEquals('peak, Should be "a"', p.peek(), 'a');
|
||||
}
|
||||
|
||||
|
||||
function testEnqueuePeek2() {
|
||||
var p = new goog.structs.PriorityQueue();
|
||||
|
||||
p.enqueue(1, 'b');
|
||||
assertEquals('peak, Should be "b"', p.peek(), 'b');
|
||||
p.enqueue(3, 'd');
|
||||
assertEquals('peak, Should be "b"', p.peek(), 'b');
|
||||
p.enqueue(0, 'a');
|
||||
assertEquals('peak, Should be "a"', p.peek(), 'a');
|
||||
p.enqueue(2, 'c');
|
||||
assertEquals('peak, Should be "a"', p.peek(), 'a');
|
||||
}
|
||||
@@ -0,0 +1,570 @@
|
||||
// Copyright 2008 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: A point Quad Tree for representing 2D data. Each
|
||||
* region has the same ratio as the bounds for the tree.
|
||||
*
|
||||
* The implementation currently requires pre-determined bounds for data as it
|
||||
* can not rebalance itself to that degree.
|
||||
*
|
||||
* @see ../demos/quadtree.html
|
||||
*/
|
||||
|
||||
|
||||
goog.provide('goog.structs.QuadTree');
|
||||
goog.provide('goog.structs.QuadTree.Node');
|
||||
goog.provide('goog.structs.QuadTree.Point');
|
||||
|
||||
goog.require('goog.math.Coordinate');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Constructs a new quad tree.
|
||||
* @param {number} minX Minimum x-value that can be held in tree.
|
||||
* @param {number} minY Minimum y-value that can be held in tree.
|
||||
* @param {number} maxX Maximum x-value that can be held in tree.
|
||||
* @param {number} maxY Maximum y-value that can be held in tree.
|
||||
* @constructor
|
||||
* @final
|
||||
*/
|
||||
goog.structs.QuadTree = function(minX, minY, maxX, maxY) {
|
||||
/**
|
||||
* Count of the number of items in the tree.
|
||||
* @private {number}
|
||||
*/
|
||||
this.count_ = 0;
|
||||
|
||||
/**
|
||||
* The root node for the quad tree.
|
||||
* @private {goog.structs.QuadTree.Node}
|
||||
*/
|
||||
this.root_ = new goog.structs.QuadTree.Node(
|
||||
minX, minY, maxX - minX, maxY - minY);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns a reference to the tree's root node. Callers shouldn't modify nodes,
|
||||
* directly. This is a convenience for visualization and debugging purposes.
|
||||
* @return {goog.structs.QuadTree.Node} The root node.
|
||||
*/
|
||||
goog.structs.QuadTree.prototype.getRootNode = function() {
|
||||
return this.root_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sets the value of an (x, y) point within the quad-tree.
|
||||
* @param {number} x The x-coordinate.
|
||||
* @param {number} y The y-coordinate.
|
||||
* @param {*} value The value associated with the point.
|
||||
*/
|
||||
goog.structs.QuadTree.prototype.set = function(x, y, value) {
|
||||
var root = this.root_;
|
||||
if (x < root.x || y < root.y || x > root.x + root.w || y > root.y + root.h) {
|
||||
throw Error('Out of bounds : (' + x + ', ' + y + ')');
|
||||
}
|
||||
if (this.insert_(root, new goog.structs.QuadTree.Point(x, y, value))) {
|
||||
this.count_++;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the value of the point at (x, y) or null if the point is empty.
|
||||
* @param {number} x The x-coordinate.
|
||||
* @param {number} y The y-coordinate.
|
||||
* @param {*=} opt_default The default value to return if the node doesn't
|
||||
* exist.
|
||||
* @return {*} The value of the node, the default value if the node
|
||||
* doesn't exist, or undefined if the node doesn't exist and no default
|
||||
* has been provided.
|
||||
*/
|
||||
goog.structs.QuadTree.prototype.get = function(x, y, opt_default) {
|
||||
var node = this.find_(this.root_, x, y);
|
||||
return node ? node.point.value : opt_default;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Removes a point from (x, y) if it exists.
|
||||
* @param {number} x The x-coordinate.
|
||||
* @param {number} y The y-coordinate.
|
||||
* @return {*} The value of the node that was removed, or null if the
|
||||
* node doesn't exist.
|
||||
*/
|
||||
goog.structs.QuadTree.prototype.remove = function(x, y) {
|
||||
var node = this.find_(this.root_, x, y);
|
||||
if (node) {
|
||||
var value = node.point.value;
|
||||
node.point = null;
|
||||
node.nodeType = goog.structs.QuadTree.NodeType.EMPTY;
|
||||
this.balance_(node);
|
||||
this.count_--;
|
||||
return value;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns true if the point at (x, y) exists in the tree.
|
||||
* @param {number} x The x-coordinate.
|
||||
* @param {number} y The y-coordinate.
|
||||
* @return {boolean} Whether the tree contains a point at (x, y).
|
||||
*/
|
||||
goog.structs.QuadTree.prototype.contains = function(x, y) {
|
||||
return this.get(x, y) != null;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {boolean} Whether the tree is empty.
|
||||
*/
|
||||
goog.structs.QuadTree.prototype.isEmpty = function() {
|
||||
return this.root_.nodeType == goog.structs.QuadTree.NodeType.EMPTY;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {number} The number of items in the tree.
|
||||
*/
|
||||
goog.structs.QuadTree.prototype.getCount = function() {
|
||||
return this.count_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Removes all items from the tree.
|
||||
*/
|
||||
goog.structs.QuadTree.prototype.clear = function() {
|
||||
this.root_.nw = this.root_.ne = this.root_.sw = this.root_.se = null;
|
||||
this.root_.nodeType = goog.structs.QuadTree.NodeType.EMPTY;
|
||||
this.root_.point = null;
|
||||
this.count_ = 0;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns an array containing the coordinates of each point stored in the tree.
|
||||
* @return {!Array<goog.math.Coordinate?>} Array of coordinates.
|
||||
*/
|
||||
goog.structs.QuadTree.prototype.getKeys = function() {
|
||||
var arr = [];
|
||||
this.traverse_(this.root_, function(node) {
|
||||
arr.push(new goog.math.Coordinate(node.point.x, node.point.y));
|
||||
});
|
||||
return arr;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns an array containing all values stored within the tree.
|
||||
* @return {!Array<Object>} The values stored within the tree.
|
||||
*/
|
||||
goog.structs.QuadTree.prototype.getValues = function() {
|
||||
var arr = [];
|
||||
this.traverse_(this.root_, function(node) {
|
||||
// Must have a point because it's a leaf.
|
||||
arr.push(node.point.value);
|
||||
});
|
||||
return arr;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Clones the quad-tree and returns the new instance.
|
||||
* @return {!goog.structs.QuadTree} A clone of the tree.
|
||||
*/
|
||||
goog.structs.QuadTree.prototype.clone = function() {
|
||||
var x1 = this.root_.x;
|
||||
var y1 = this.root_.y;
|
||||
var x2 = x1 + this.root_.w;
|
||||
var y2 = y1 + this.root_.h;
|
||||
var clone = new goog.structs.QuadTree(x1, y1, x2, y2);
|
||||
// This is inefficient as the clone needs to recalculate the structure of the
|
||||
// tree, even though we know it already. But this is easier and can be
|
||||
// optimized when/if needed.
|
||||
this.traverse_(this.root_, function(node) {
|
||||
clone.set(node.point.x, node.point.y, node.point.value);
|
||||
});
|
||||
return clone;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Traverses the tree and calls a function on each node.
|
||||
* @param {function(?, goog.math.Coordinate, goog.structs.QuadTree)} fn
|
||||
* The function to call for every value. This function takes 3 arguments
|
||||
* (the value, the coordinate, and the tree itself) and the return value is
|
||||
* irrelevant.
|
||||
* @param {Object=} opt_obj The object to be used as the value of 'this'
|
||||
* within {@ code fn}.
|
||||
*/
|
||||
goog.structs.QuadTree.prototype.forEach = function(fn, opt_obj) {
|
||||
this.traverse_(this.root_, function(node) {
|
||||
var coord = new goog.math.Coordinate(node.point.x, node.point.y);
|
||||
fn.call(opt_obj, node.point.value, coord, this);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Traverses the tree depth-first, with quadrants being traversed in clockwise
|
||||
* order (NE, SE, SW, NW). The provided function will be called for each
|
||||
* leaf node that is encountered.
|
||||
* @param {goog.structs.QuadTree.Node} node The current node.
|
||||
* @param {function(goog.structs.QuadTree.Node)} fn The function to call
|
||||
* for each leaf node. This function takes the node as an argument, and its
|
||||
* return value is irrelevant.
|
||||
* @private
|
||||
*/
|
||||
goog.structs.QuadTree.prototype.traverse_ = function(node, fn) {
|
||||
switch (node.nodeType) {
|
||||
case goog.structs.QuadTree.NodeType.LEAF:
|
||||
fn.call(this, node);
|
||||
break;
|
||||
|
||||
case goog.structs.QuadTree.NodeType.POINTER:
|
||||
this.traverse_(node.ne, fn);
|
||||
this.traverse_(node.se, fn);
|
||||
this.traverse_(node.sw, fn);
|
||||
this.traverse_(node.nw, fn);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Finds a leaf node with the same (x, y) coordinates as the target point, or
|
||||
* null if no point exists.
|
||||
* @param {goog.structs.QuadTree.Node} node The node to search in.
|
||||
* @param {number} x The x-coordinate of the point to search for.
|
||||
* @param {number} y The y-coordinate of the point to search for.
|
||||
* @return {goog.structs.QuadTree.Node} The leaf node that matches the target,
|
||||
* or null if it doesn't exist.
|
||||
* @private
|
||||
*/
|
||||
goog.structs.QuadTree.prototype.find_ = function(node, x, y) {
|
||||
switch (node.nodeType) {
|
||||
case goog.structs.QuadTree.NodeType.EMPTY:
|
||||
return null;
|
||||
|
||||
case goog.structs.QuadTree.NodeType.LEAF:
|
||||
return node.point.x == x && node.point.y == y ? node : null;
|
||||
|
||||
case goog.structs.QuadTree.NodeType.POINTER:
|
||||
return this.find_(this.getQuadrantForPoint_(node, x, y), x, y);
|
||||
|
||||
default:
|
||||
throw Error('Invalid nodeType');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Inserts a point into the tree, updating the tree's structure if necessary.
|
||||
* @param {goog.structs.QuadTree.Node} parent The parent to insert the point
|
||||
* into.
|
||||
* @param {goog.structs.QuadTree.Point} point The point to insert.
|
||||
* @return {boolean} True if a new node was added to the tree; False if a node
|
||||
* already existed with the correpsonding coordinates and had its value
|
||||
* reset.
|
||||
* @private
|
||||
*/
|
||||
goog.structs.QuadTree.prototype.insert_ = function(parent, point) {
|
||||
switch (parent.nodeType) {
|
||||
case goog.structs.QuadTree.NodeType.EMPTY:
|
||||
this.setPointForNode_(parent, point);
|
||||
return true;
|
||||
|
||||
case goog.structs.QuadTree.NodeType.LEAF:
|
||||
if (parent.point.x == point.x && parent.point.y == point.y) {
|
||||
this.setPointForNode_(parent, point);
|
||||
return false;
|
||||
} else {
|
||||
this.split_(parent);
|
||||
return this.insert_(parent, point);
|
||||
}
|
||||
|
||||
case goog.structs.QuadTree.NodeType.POINTER:
|
||||
return this.insert_(
|
||||
this.getQuadrantForPoint_(parent, point.x, point.y), point);
|
||||
|
||||
default:
|
||||
throw Error('Invalid nodeType in parent');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Converts a leaf node to a pointer node and reinserts the node's point into
|
||||
* the correct child.
|
||||
* @param {goog.structs.QuadTree.Node} node The node to split.
|
||||
* @private
|
||||
*/
|
||||
goog.structs.QuadTree.prototype.split_ = function(node) {
|
||||
var oldPoint = node.point;
|
||||
node.point = null;
|
||||
|
||||
node.nodeType = goog.structs.QuadTree.NodeType.POINTER;
|
||||
|
||||
var x = node.x;
|
||||
var y = node.y;
|
||||
var hw = node.w / 2;
|
||||
var hh = node.h / 2;
|
||||
|
||||
node.nw = new goog.structs.QuadTree.Node(x, y, hw, hh, node);
|
||||
node.ne = new goog.structs.QuadTree.Node(x + hw, y, hw, hh, node);
|
||||
node.sw = new goog.structs.QuadTree.Node(x, y + hh, hw, hh, node);
|
||||
node.se = new goog.structs.QuadTree.Node(x + hw, y + hh, hw, hh, node);
|
||||
|
||||
this.insert_(node, oldPoint);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Attempts to balance a node. A node will need balancing if all its children
|
||||
* are empty or it contains just one leaf.
|
||||
* @param {goog.structs.QuadTree.Node} node The node to balance.
|
||||
* @private
|
||||
*/
|
||||
goog.structs.QuadTree.prototype.balance_ = function(node) {
|
||||
switch (node.nodeType) {
|
||||
case goog.structs.QuadTree.NodeType.EMPTY:
|
||||
case goog.structs.QuadTree.NodeType.LEAF:
|
||||
if (node.parent) {
|
||||
this.balance_(node.parent);
|
||||
}
|
||||
break;
|
||||
|
||||
case goog.structs.QuadTree.NodeType.POINTER:
|
||||
var nw = node.nw, ne = node.ne, sw = node.sw, se = node.se;
|
||||
var firstLeaf = null;
|
||||
|
||||
// Look for the first non-empty child, if there is more than one then we
|
||||
// break as this node can't be balanced.
|
||||
if (nw.nodeType != goog.structs.QuadTree.NodeType.EMPTY) {
|
||||
firstLeaf = nw;
|
||||
}
|
||||
if (ne.nodeType != goog.structs.QuadTree.NodeType.EMPTY) {
|
||||
if (firstLeaf) {
|
||||
break;
|
||||
}
|
||||
firstLeaf = ne;
|
||||
}
|
||||
if (sw.nodeType != goog.structs.QuadTree.NodeType.EMPTY) {
|
||||
if (firstLeaf) {
|
||||
break;
|
||||
}
|
||||
firstLeaf = sw;
|
||||
}
|
||||
if (se.nodeType != goog.structs.QuadTree.NodeType.EMPTY) {
|
||||
if (firstLeaf) {
|
||||
break;
|
||||
}
|
||||
firstLeaf = se;
|
||||
}
|
||||
|
||||
if (!firstLeaf) {
|
||||
// All child nodes are empty: so make this node empty.
|
||||
node.nodeType = goog.structs.QuadTree.NodeType.EMPTY;
|
||||
node.nw = node.ne = node.sw = node.se = null;
|
||||
|
||||
} else if (firstLeaf.nodeType == goog.structs.QuadTree.NodeType.POINTER) {
|
||||
// Only child was a pointer, therefore we can't rebalance.
|
||||
break;
|
||||
|
||||
} else {
|
||||
// Only child was a leaf: so update node's point and make it a leaf.
|
||||
node.nodeType = goog.structs.QuadTree.NodeType.LEAF;
|
||||
node.nw = node.ne = node.sw = node.se = null;
|
||||
node.point = firstLeaf.point;
|
||||
}
|
||||
|
||||
// Try and balance the parent as well.
|
||||
if (node.parent) {
|
||||
this.balance_(node.parent);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the child quadrant within a node that contains the given (x, y)
|
||||
* coordinate.
|
||||
* @param {goog.structs.QuadTree.Node} parent The node.
|
||||
* @param {number} x The x-coordinate to look for.
|
||||
* @param {number} y The y-coordinate to look for.
|
||||
* @return {goog.structs.QuadTree.Node} The child quadrant that contains the
|
||||
* point.
|
||||
* @private
|
||||
*/
|
||||
goog.structs.QuadTree.prototype.getQuadrantForPoint_ = function(parent, x, y) {
|
||||
var mx = parent.x + parent.w / 2;
|
||||
var my = parent.y + parent.h / 2;
|
||||
if (x < mx) {
|
||||
return y < my ? parent.nw : parent.sw;
|
||||
} else {
|
||||
return y < my ? parent.ne : parent.se;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sets the point for a node, as long as the node is a leaf or empty.
|
||||
* @param {goog.structs.QuadTree.Node} node The node to set the point for.
|
||||
* @param {goog.structs.QuadTree.Point} point The point to set.
|
||||
* @private
|
||||
*/
|
||||
goog.structs.QuadTree.prototype.setPointForNode_ = function(node, point) {
|
||||
if (node.nodeType == goog.structs.QuadTree.NodeType.POINTER) {
|
||||
throw Error('Can not set point for node of type POINTER');
|
||||
}
|
||||
node.nodeType = goog.structs.QuadTree.NodeType.LEAF;
|
||||
node.point = point;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Enumeration of node types.
|
||||
* @enum {number}
|
||||
*/
|
||||
goog.structs.QuadTree.NodeType = {
|
||||
EMPTY: 0,
|
||||
LEAF: 1,
|
||||
POINTER: 2
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Constructs a new quad tree node.
|
||||
* @param {number} x X-coordiate of node.
|
||||
* @param {number} y Y-coordinate of node.
|
||||
* @param {number} w Width of node.
|
||||
* @param {number} h Height of node.
|
||||
* @param {goog.structs.QuadTree.Node=} opt_parent Optional parent node.
|
||||
* @constructor
|
||||
* @final
|
||||
*/
|
||||
goog.structs.QuadTree.Node = function(x, y, w, h, opt_parent) {
|
||||
/**
|
||||
* The x-coordinate of the node.
|
||||
* @type {number}
|
||||
*/
|
||||
this.x = x;
|
||||
|
||||
/**
|
||||
* The y-coordinate of the node.
|
||||
* @type {number}
|
||||
*/
|
||||
this.y = y;
|
||||
|
||||
/**
|
||||
* The width of the node.
|
||||
* @type {number}
|
||||
*/
|
||||
this.w = w;
|
||||
|
||||
/**
|
||||
* The height of the node.
|
||||
* @type {number}
|
||||
*/
|
||||
this.h = h;
|
||||
|
||||
/**
|
||||
* The parent node.
|
||||
* @type {goog.structs.QuadTree.Node?}
|
||||
*/
|
||||
this.parent = opt_parent || null;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* The node's type.
|
||||
* @type {goog.structs.QuadTree.NodeType}
|
||||
*/
|
||||
goog.structs.QuadTree.Node.prototype.nodeType =
|
||||
goog.structs.QuadTree.NodeType.EMPTY;
|
||||
|
||||
|
||||
/**
|
||||
* The child node in the North-West quadrant.
|
||||
* @type {goog.structs.QuadTree.Node?}
|
||||
*/
|
||||
goog.structs.QuadTree.Node.prototype.nw = null;
|
||||
|
||||
|
||||
/**
|
||||
* The child node in the North-East quadrant.
|
||||
* @type {goog.structs.QuadTree.Node?}
|
||||
*/
|
||||
goog.structs.QuadTree.Node.prototype.ne = null;
|
||||
|
||||
|
||||
/**
|
||||
* The child node in the South-West quadrant.
|
||||
* @type {goog.structs.QuadTree.Node?}
|
||||
*/
|
||||
goog.structs.QuadTree.Node.prototype.sw = null;
|
||||
|
||||
|
||||
/**
|
||||
* The child node in the South-East quadrant.
|
||||
* @type {goog.structs.QuadTree.Node?}
|
||||
*/
|
||||
goog.structs.QuadTree.Node.prototype.se = null;
|
||||
|
||||
|
||||
/**
|
||||
* The point for the node, if it is a leaf node.
|
||||
* @type {goog.structs.QuadTree.Point?}
|
||||
*/
|
||||
goog.structs.QuadTree.Node.prototype.point = null;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new point object.
|
||||
* @param {number} x The x-coordinate of the point.
|
||||
* @param {number} y The y-coordinate of the point.
|
||||
* @param {*=} opt_value Optional value associated with the point.
|
||||
* @constructor
|
||||
* @final
|
||||
*/
|
||||
goog.structs.QuadTree.Point = function(x, y, opt_value) {
|
||||
/**
|
||||
* The x-coordinate for the point.
|
||||
* @type {number}
|
||||
*/
|
||||
this.x = x;
|
||||
|
||||
/**
|
||||
* The y-coordinate for the point.
|
||||
* @type {number}
|
||||
*/
|
||||
this.y = y;
|
||||
|
||||
/**
|
||||
* Optional value associated with the point.
|
||||
* @type {*}
|
||||
*/
|
||||
this.value = goog.isDef(opt_value) ? opt_value : null;
|
||||
};
|
||||
@@ -0,0 +1,22 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
Copyright 2008 The Closure Library Authors. All Rights Reserved.
|
||||
|
||||
Use of this source code is governed by the Apache License, Version 2.0.
|
||||
See the COPYING file for details.
|
||||
-->
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<title>
|
||||
Closure Unit Tests - goog.structs.QuadTree
|
||||
</title>
|
||||
<script src="../base.js">
|
||||
</script>
|
||||
<script>
|
||||
goog.require('goog.structs.QuadTreeTest');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,174 @@
|
||||
// Copyright 2008 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.
|
||||
|
||||
goog.provide('goog.structs.QuadTreeTest');
|
||||
goog.setTestOnly('goog.structs.QuadTreeTest');
|
||||
|
||||
goog.require('goog.structs');
|
||||
goog.require('goog.structs.QuadTree');
|
||||
goog.require('goog.testing.jsunit');
|
||||
|
||||
function getTree() {
|
||||
var qt = new goog.structs.QuadTree(0, 0, 100, 100);
|
||||
qt.set(5, 20, 'Foo');
|
||||
qt.set(50, 32, 'Bar');
|
||||
qt.set(47, 96, 'Baz');
|
||||
qt.set(50, 50, 'Bing');
|
||||
qt.set(12, 0, 'Bong');
|
||||
return qt;
|
||||
}
|
||||
|
||||
function testGetCount() {
|
||||
var qt = getTree();
|
||||
assertEquals('Count should be 5', 5, qt.getCount());
|
||||
qt.remove(50, 32);
|
||||
assertEquals('Count should be 4', 4, qt.getCount());
|
||||
}
|
||||
|
||||
function testGetKeys() {
|
||||
var keys = getTree().getKeys();
|
||||
var keyString = keys.sort().join(' ');
|
||||
var expected = '(12, 0) (47, 96) (5, 20) (50, 32) (50, 50)';
|
||||
assertEquals('Sorted keys should be ' + expected, expected, keyString);
|
||||
}
|
||||
|
||||
function testGetValues() {
|
||||
var values = getTree().getValues();
|
||||
var valueString = values.sort().join(',');
|
||||
assertEquals('Sorted values should be Bar,Baz,Bing,Bong,Foo',
|
||||
'Bar,Baz,Bing,Bong,Foo', valueString);
|
||||
}
|
||||
|
||||
function testContains() {
|
||||
var qt = getTree();
|
||||
assertTrue('Should contain (5, 20)', qt.contains(5, 20));
|
||||
assertFalse('Should not contain (13, 13)', qt.contains(13, 13));
|
||||
}
|
||||
|
||||
function testClear() {
|
||||
var qt = getTree();
|
||||
qt.clear();
|
||||
assertTrue('Tree should be empty', qt.isEmpty());
|
||||
assertFalse('Tree should not contain (5, 20)', qt.contains(5, 20));
|
||||
}
|
||||
|
||||
function testConstructor() {
|
||||
var qt = new goog.structs.QuadTree(-10, -5, 6, 12);
|
||||
var root = qt.getRootNode();
|
||||
assertEquals('X of root should be -10', -10, root.x);
|
||||
assertEquals('Y of root should be -5', -5, root.y);
|
||||
assertEquals('Width of root should be 16', 16, root.w);
|
||||
assertEquals('Height of root should be 17', 17, root.h);
|
||||
assertTrue('Tree should be empty', qt.isEmpty());
|
||||
}
|
||||
|
||||
function testClone() {
|
||||
var qt = getTree().clone();
|
||||
assertFalse('Clone should not be empty', qt.isEmpty());
|
||||
assertTrue('Should contain (47, 96)', qt.contains(47, 96));
|
||||
}
|
||||
|
||||
function testRemove() {
|
||||
var qt = getTree();
|
||||
assertEquals('(5, 20) should be removed', 'Foo', qt.remove(5, 20));
|
||||
assertEquals('(5, 20) should be removed', 'Bar', qt.remove(50, 32));
|
||||
assertEquals('(5, 20) should be removed', 'Baz', qt.remove(47, 96));
|
||||
assertEquals('(5, 20) should be removed', 'Bing', qt.remove(50, 50));
|
||||
assertEquals('(5, 20) should be removed', 'Bong', qt.remove(12, 0));
|
||||
assertNull('(6, 6) wasn\'t there to remove', qt.remove(6, 6));
|
||||
assertTrue('Tree should be empty', qt.isEmpty());
|
||||
assertTreesChildrenAreNull(qt);
|
||||
}
|
||||
|
||||
function testIsEmpty() {
|
||||
var qt = getTree();
|
||||
qt.clear();
|
||||
assertTrue(qt.isEmpty());
|
||||
assertEquals('Root should be empty node',
|
||||
goog.structs.QuadTree.NodeType.EMPTY, qt.getRootNode().nodeType);
|
||||
assertTreesChildrenAreNull(qt);
|
||||
}
|
||||
|
||||
function testForEach() {
|
||||
var qt = getTree();
|
||||
var s = '';
|
||||
goog.structs.forEach(qt, function(val, key, qt2) {
|
||||
assertNotUndefined(key);
|
||||
assertEquals(qt, qt2);
|
||||
s += key.x + ',' + key.y + '=' + val + ' ';
|
||||
});
|
||||
assertEquals('50,32=Bar 50,50=Bing 47,96=Baz 5,20=Foo 12,0=Bong ', s);
|
||||
}
|
||||
|
||||
function testBalancing() {
|
||||
var qt = new goog.structs.QuadTree(0, 0, 100, 100);
|
||||
var root = qt.getRootNode();
|
||||
|
||||
// Add a point to the NW quadrant.
|
||||
qt.set(25, 25, 'first');
|
||||
|
||||
assertEquals('Root should be a leaf node.',
|
||||
goog.structs.QuadTree.NodeType.LEAF, root.nodeType);
|
||||
assertTreesChildrenAreNull(qt);
|
||||
|
||||
assertEquals('first', root.point.value);
|
||||
|
||||
// Add another point in the NW quadrant
|
||||
qt.set(25, 30, 'second');
|
||||
|
||||
assertEquals('Root should now be a pointer.',
|
||||
goog.structs.QuadTree.NodeType.POINTER, root.nodeType);
|
||||
assertNotNull('NE should be not be null', root.ne);
|
||||
assertNotNull('NW should be not be null', root.nw);
|
||||
assertNotNull('SE should be not be null', root.se);
|
||||
assertNotNull('SW should be not be null', root.sw);
|
||||
assertNull(root.point);
|
||||
|
||||
// Delete the second point.
|
||||
qt.remove(25, 30);
|
||||
|
||||
assertEquals('Root should have been rebalanced and be a leaf node.',
|
||||
goog.structs.QuadTree.NodeType.LEAF, root.nodeType);
|
||||
assertTreesChildrenAreNull(qt);
|
||||
assertEquals('first', root.point.value);
|
||||
}
|
||||
|
||||
function testTreeBounds() {
|
||||
var qt = getTree();
|
||||
assertFails(qt, qt.set, [-10, -10, 1]);
|
||||
assertFails(qt, qt.set, [-10, 10, 2]);
|
||||
assertFails(qt, qt.set, [10, -10, 3]);
|
||||
assertFails(qt, qt.set, [-10, 110, 4]);
|
||||
assertFails(qt, qt.set, [10, 130, 5]);
|
||||
assertFails(qt, qt.set, [110, -10, 6]);
|
||||
assertFails(qt, qt.set, [150, 14, 7]);
|
||||
}
|
||||
|
||||
// Helper functions
|
||||
|
||||
function assertFails(context, fn, args) {
|
||||
assertThrows('Exception expected from ' + fn.toString() +
|
||||
' with arguments ' + args,
|
||||
function() {
|
||||
fn.apply(context, args);
|
||||
});
|
||||
}
|
||||
|
||||
function assertTreesChildrenAreNull(qt) {
|
||||
var root = qt.getRootNode();
|
||||
assertNull('NE should be null', root.ne);
|
||||
assertNull('NW should be null', root.nw);
|
||||
assertNull('SE should be null', root.se);
|
||||
assertNull('SW should be null', root.sw);
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
// Copyright 2006 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: Queue.
|
||||
*
|
||||
*
|
||||
* This file provides the implementation of a FIFO Queue structure.
|
||||
* API is similar to that of com.google.common.collect.IntQueue
|
||||
*
|
||||
* The implementation is a classic 2-stack queue.
|
||||
* There's a "front" stack and a "back" stack.
|
||||
* Items are pushed onto "back" and popped from "front".
|
||||
* When "front" is empty, we replace "front" with reverse(back).
|
||||
*
|
||||
* Example:
|
||||
* front back op
|
||||
* [] [] enqueue 1
|
||||
* [] [1] enqueue 2
|
||||
* [] [1,2] enqueue 3
|
||||
* [] [1,2,3] dequeue -> ...
|
||||
* [3,2,1] [] ... -> 1
|
||||
* [3,2] [] enqueue 4
|
||||
* [3,2] [4] dequeue -> 2
|
||||
* [3] [4]
|
||||
*
|
||||
* Front and back are simple javascript arrays. We rely on
|
||||
* Array.push and Array.pop being O(1) amortized.
|
||||
*
|
||||
* Note: In V8, queues, up to a certain size, can be implemented
|
||||
* just fine using Array.push and Array.shift, but other JavaScript
|
||||
* engines do not have the optimization of Array.shift.
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.structs.Queue');
|
||||
|
||||
goog.require('goog.array');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Class for FIFO Queue data structure.
|
||||
*
|
||||
* @constructor
|
||||
* @template T
|
||||
*/
|
||||
goog.structs.Queue = function() {
|
||||
/**
|
||||
* @private {!Array<T>} Front stack. Items are pop()'ed from here.
|
||||
*/
|
||||
this.front_ = [];
|
||||
/**
|
||||
* @private {!Array<T>} Back stack. Items are push()'ed here.
|
||||
*/
|
||||
this.back_ = [];
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Flips the back stack onto the front stack if front is empty,
|
||||
* to prepare for peek() or dequeue().
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
goog.structs.Queue.prototype.maybeFlip_ = function() {
|
||||
if (goog.array.isEmpty(this.front_)) {
|
||||
this.front_ = this.back_;
|
||||
this.front_.reverse();
|
||||
this.back_ = [];
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Puts the specified element on this queue.
|
||||
* @param {T} element The element to be added to the queue.
|
||||
*/
|
||||
goog.structs.Queue.prototype.enqueue = function(element) {
|
||||
this.back_.push(element);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Retrieves and removes the head of this queue.
|
||||
* @return {T} The element at the head of this queue. Returns undefined if the
|
||||
* queue is empty.
|
||||
*/
|
||||
goog.structs.Queue.prototype.dequeue = function() {
|
||||
this.maybeFlip_();
|
||||
return this.front_.pop();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Retrieves but does not remove the head of this queue.
|
||||
* @return {T} The element at the head of this queue. Returns undefined if the
|
||||
* queue is empty.
|
||||
*/
|
||||
goog.structs.Queue.prototype.peek = function() {
|
||||
this.maybeFlip_();
|
||||
return goog.array.peek(this.front_);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the number of elements in this queue.
|
||||
* @return {number} The number of elements in this queue.
|
||||
*/
|
||||
goog.structs.Queue.prototype.getCount = function() {
|
||||
return this.front_.length + this.back_.length;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns true if this queue contains no elements.
|
||||
* @return {boolean} true if this queue contains no elements.
|
||||
*/
|
||||
goog.structs.Queue.prototype.isEmpty = function() {
|
||||
return goog.array.isEmpty(this.front_) &&
|
||||
goog.array.isEmpty(this.back_);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Removes all elements from the queue.
|
||||
*/
|
||||
goog.structs.Queue.prototype.clear = function() {
|
||||
this.front_ = [];
|
||||
this.back_ = [];
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns true if the given value is in the queue.
|
||||
* @param {T} obj The value to look for.
|
||||
* @return {boolean} Whether the object is in the queue.
|
||||
*/
|
||||
goog.structs.Queue.prototype.contains = function(obj) {
|
||||
return goog.array.contains(this.front_, obj) ||
|
||||
goog.array.contains(this.back_, obj);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Removes the first occurrence of a particular value from the queue.
|
||||
* @param {T} obj Object to remove.
|
||||
* @return {boolean} True if an element was removed.
|
||||
*/
|
||||
goog.structs.Queue.prototype.remove = function(obj) {
|
||||
// TODO(user): Implement goog.array.removeLast() and use it here.
|
||||
var index = goog.array.lastIndexOf(this.front_, obj);
|
||||
if (index < 0) {
|
||||
return goog.array.remove(this.back_, obj);
|
||||
}
|
||||
goog.array.removeAt(this.front_, index);
|
||||
return true;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns all the values in the queue.
|
||||
* @return {!Array<T>} An array of the values in the queue.
|
||||
*/
|
||||
goog.structs.Queue.prototype.getValues = function() {
|
||||
var res = [];
|
||||
// Add the front array in reverse, then the back array.
|
||||
for (var i = this.front_.length - 1; i >= 0; --i) {
|
||||
res.push(this.front_[i]);
|
||||
}
|
||||
var len = this.back_.length;
|
||||
for (var i = 0; i < len; ++i) {
|
||||
res.push(this.back_[i]);
|
||||
}
|
||||
return res;
|
||||
};
|
||||
@@ -0,0 +1,126 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
Copyright 2014 The Closure Library Authors. All Rights Reserved.
|
||||
|
||||
Use of this source code is governed by the Apache License, Version 2.0.
|
||||
See the COPYING file for details.
|
||||
-->
|
||||
<!--
|
||||
-->
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<title>Closure Performance Tests - goog.structs.Queue</title>
|
||||
<link rel="stylesheet" type="text/css" href="../testing/performancetable.css"/>
|
||||
<script src="../base.js"></script>
|
||||
<script>
|
||||
goog.require('goog.functions');
|
||||
goog.require('goog.structs.Queue');
|
||||
goog.require('goog.testing.PerformanceTable');
|
||||
goog.require('goog.testing.jsunit');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<h1>goog.structs.Queue Performance Tests</h1>
|
||||
<p>
|
||||
<strong>User-agent:</strong>
|
||||
<script>document.write(navigator.userAgent);</script>
|
||||
</p>
|
||||
<div id="perfTable"></div>
|
||||
<hr>
|
||||
|
||||
<script>
|
||||
var table = new goog.testing.PerformanceTable(
|
||||
goog.dom.getElement('perfTable'));
|
||||
|
||||
// Number of operations to measure in each table line.
|
||||
var OPS_COUNT = 10000;
|
||||
|
||||
var smallSizes = [1, 2, 5, 10, 20, 50, 100, 1000, 10000];
|
||||
var largeSizes = [100000];
|
||||
|
||||
function populateQueue(size) {
|
||||
var q = new goog.structs.Queue();
|
||||
for (var i = 0; i < size; ++i) {
|
||||
q.enqueue(i);
|
||||
}
|
||||
return q;
|
||||
}
|
||||
|
||||
function enqueueDequeueTest(size) {
|
||||
var q = populateQueue(size);
|
||||
table.run(function() {
|
||||
for (var i = 0; i < OPS_COUNT; ++i) {
|
||||
q.dequeue();
|
||||
q.enqueue(i);
|
||||
}
|
||||
}, 'Enqueue and dequeue, size ' + size);
|
||||
}
|
||||
|
||||
function containsTest(size) {
|
||||
var q = populateQueue(size);
|
||||
table.run(function() {
|
||||
for (var i = 0; i < OPS_COUNT; ++i) {
|
||||
q.contains(i);
|
||||
}
|
||||
}, 'Contains every element, size ' + size);
|
||||
}
|
||||
|
||||
function removeTest(size) {
|
||||
var q = populateQueue(size);
|
||||
if (size == 1) {
|
||||
return;
|
||||
}
|
||||
table.run(function() {
|
||||
var offset = Math.round(size / 2);
|
||||
for (var i = 0; i < OPS_COUNT; ++i) {
|
||||
q.remove(i + offset);
|
||||
q.enqueue(i + size);
|
||||
}
|
||||
}, 'Remove element from the middle, size ' + size);
|
||||
}
|
||||
|
||||
function getValuesTest(size) {
|
||||
var q = populateQueue(size);
|
||||
table.run(function() {
|
||||
for (var i = 0; i < OPS_COUNT; ++i) {
|
||||
q.getValues()[size - 1];
|
||||
}
|
||||
}, 'Get values, size ' + size);
|
||||
}
|
||||
|
||||
function testEnqueueDequeueSmall() {
|
||||
goog.array.forEach(smallSizes, enqueueDequeueTest);
|
||||
}
|
||||
|
||||
function testEnqueueDequeueLarge() {
|
||||
goog.array.forEach(largeSizes, enqueueDequeueTest);
|
||||
}
|
||||
|
||||
function testContainsSmall() {
|
||||
goog.array.forEach(smallSizes, containsTest);
|
||||
}
|
||||
|
||||
function testContainsLarge() {
|
||||
goog.array.forEach(largeSizes, containsTest);
|
||||
}
|
||||
|
||||
function testRemoveSmall() {
|
||||
goog.array.forEach(smallSizes, removeTest);
|
||||
}
|
||||
|
||||
function testRemoveLarge() {
|
||||
goog.array.forEach(largeSizes, removeTest);
|
||||
}
|
||||
|
||||
function testGetValuesSmall() {
|
||||
goog.array.forEach(smallSizes, getValuesTest);
|
||||
}
|
||||
|
||||
function testGetValuesLarge() {
|
||||
goog.array.forEach(largeSizes, getValuesTest);
|
||||
}
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,22 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
Copyright 2006 The Closure Library Authors. All Rights Reserved.
|
||||
|
||||
Use of this source code is governed by the Apache License, Version 2.0.
|
||||
See the COPYING file for details.
|
||||
-->
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<title>
|
||||
Closure Unit Tests - goog.structs.Queue
|
||||
</title>
|
||||
<script src="../base.js">
|
||||
</script>
|
||||
<script>
|
||||
goog.require('goog.structs.QueueTest');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,161 @@
|
||||
// Copyright 2006 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.
|
||||
|
||||
goog.provide('goog.structs.QueueTest');
|
||||
goog.setTestOnly('goog.structs.QueueTest');
|
||||
|
||||
goog.require('goog.structs.Queue');
|
||||
goog.require('goog.testing.jsunit');
|
||||
|
||||
function stringifyQueue(q) {
|
||||
var values = q.getValues();
|
||||
var s = '';
|
||||
for (var i = 0; i < values.length; i++) {
|
||||
s += values[i];
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
function createQueue() {
|
||||
var q = new goog.structs.Queue();
|
||||
q.enqueue('a');
|
||||
q.enqueue('b');
|
||||
q.enqueue('c');
|
||||
q.enqueue('a');
|
||||
q.dequeue();
|
||||
q.enqueue('b');
|
||||
q.enqueue('c');
|
||||
// q is now: bcabc
|
||||
return q;
|
||||
}
|
||||
|
||||
function testConstructor() {
|
||||
var q = new goog.structs.Queue();
|
||||
assertTrue('testConstructor(), queue should be empty initially', q.isEmpty());
|
||||
assertEquals('testConstructor(), count should be 0', q.getCount(), 0);
|
||||
assertEquals('testConstructor(), head element should be undefined', q.peek(),
|
||||
undefined);
|
||||
}
|
||||
|
||||
function testCount() {
|
||||
var q = createQueue();
|
||||
assertEquals('testCount(), count should be 5', q.getCount(), 5);
|
||||
q.enqueue('d');
|
||||
assertEquals('testCount(), count should be 6', q.getCount(), 6);
|
||||
q.dequeue();
|
||||
assertEquals('testCount(), count should be 5', q.getCount(), 5);
|
||||
q.clear();
|
||||
assertEquals('testCount(), count should be 0', q.getCount(), 0);
|
||||
}
|
||||
|
||||
function testEnqueue() {
|
||||
var q = new goog.structs.Queue();
|
||||
q.enqueue('a');
|
||||
assertEquals('testEnqueue(), count should be 1', q.getCount(), 1);
|
||||
q.enqueue('b');
|
||||
assertEquals('testEnqueue(), count should be 2', q.getCount(), 2);
|
||||
assertEquals('testEnqueue(), head element should be a', q.peek(), 'a');
|
||||
q.dequeue();
|
||||
assertEquals('testEnqueue(), count should be 1', q.getCount(), 1);
|
||||
assertEquals('testEnqueue(), head element should be b', q.peek(), 'b');
|
||||
}
|
||||
|
||||
function testDequeue() {
|
||||
var q = createQueue();
|
||||
assertEquals('testDequeue(), should return b', q.dequeue(), 'b');
|
||||
assertEquals('testDequeue(), should return b', q.dequeue(), 'c');
|
||||
assertEquals('testDequeue(), should return b', q.dequeue(), 'a');
|
||||
assertEquals('testDequeue(), should return b', q.dequeue(), 'b');
|
||||
assertEquals('testDequeue(), should return b', q.dequeue(), 'c');
|
||||
assertTrue('testDequeue(), queue should be empty', q.isEmpty());
|
||||
assertEquals('testDequeue(), should return undefined for empty queue',
|
||||
q.dequeue(), undefined);
|
||||
}
|
||||
|
||||
function testPeek() {
|
||||
var q = createQueue();
|
||||
assertEquals('testPeek(), should return b', q.peek(), 'b');
|
||||
assertEquals('testPeek(), dequeue should return peek() result',
|
||||
q.dequeue(), 'b');
|
||||
assertEquals('testPeek(), should return b', q.peek(), 'c');
|
||||
q.clear();
|
||||
assertEquals('testPeek(), should return undefined for empty queue',
|
||||
q.peek(), undefined);
|
||||
}
|
||||
|
||||
function testClear() {
|
||||
var q = createQueue();
|
||||
q.clear();
|
||||
assertTrue('testClear(), queue should be empty', q.isEmpty());
|
||||
}
|
||||
|
||||
function testQueue() {
|
||||
var q = createQueue();
|
||||
assertEquals('testQueue(), contents must be bcabc',
|
||||
stringifyQueue(q), 'bcabc');
|
||||
}
|
||||
|
||||
function testRemove() {
|
||||
var q = createQueue();
|
||||
assertEquals('testRemove(), contents must be bcabc',
|
||||
stringifyQueue(q), 'bcabc');
|
||||
|
||||
q.dequeue();
|
||||
assertEquals('testRemove(), contents must be cabc',
|
||||
stringifyQueue(q), 'cabc');
|
||||
|
||||
q.enqueue('a');
|
||||
assertEquals('testRemove(), contents must be cabca',
|
||||
stringifyQueue(q), 'cabca');
|
||||
|
||||
assertTrue('testRemove(), remove should have returned true', q.remove('c'));
|
||||
assertEquals('testRemove(), contents must be abca',
|
||||
stringifyQueue(q), 'abca');
|
||||
|
||||
assertTrue('testRemove(), remove should have returned true', q.remove('b'));
|
||||
assertEquals('testRemove(), contents must be aca', stringifyQueue(q), 'aca');
|
||||
|
||||
assertFalse('testRemove(), remove should have returned false', q.remove('b'));
|
||||
assertEquals('testRemove(), contents must be aca', stringifyQueue(q), 'aca');
|
||||
|
||||
assertTrue('testRemove(), remove should have returned true', q.remove('a'));
|
||||
assertEquals('testRemove(), contents must be ca', stringifyQueue(q), 'ca');
|
||||
|
||||
assertTrue('testRemove(), remove should have returned true', q.remove('a'));
|
||||
assertEquals('testRemove(), contents must be c', stringifyQueue(q), 'c');
|
||||
|
||||
assertTrue('testRemove(), remove should have returned true', q.remove('c'));
|
||||
assertEquals('testRemove(), contents must be empty', stringifyQueue(q), '');
|
||||
|
||||
q.enqueue('a');
|
||||
q.enqueue('b');
|
||||
q.enqueue('c');
|
||||
q.enqueue('a');
|
||||
q.dequeue();
|
||||
q.enqueue('b');
|
||||
q.enqueue('c');
|
||||
assertEquals('testRemove(), contents must be bcabc',
|
||||
stringifyQueue(q), 'bcabc');
|
||||
assertTrue('testRemove(), remove should have returned true', q.remove('c'));
|
||||
assertEquals('testRemove(), contents must be babc',
|
||||
stringifyQueue(q), 'babc');
|
||||
}
|
||||
|
||||
function testContains() {
|
||||
var q = createQueue();
|
||||
assertTrue('testContains(), contains should have returned true',
|
||||
q.contains('a'));
|
||||
assertFalse('testContains(), contains should have returned false',
|
||||
q.contains('foobar'));
|
||||
}
|
||||
@@ -0,0 +1,279 @@
|
||||
// Copyright 2006 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: Set.
|
||||
*
|
||||
* @author arv@google.com (Erik Arvidsson)
|
||||
*
|
||||
* This class implements a set data structure. Adding and removing is O(1). It
|
||||
* supports both object and primitive values. Be careful because you can add
|
||||
* both 1 and new Number(1), because these are not the same. You can even add
|
||||
* multiple new Number(1) because these are not equal.
|
||||
*/
|
||||
|
||||
|
||||
goog.provide('goog.structs.Set');
|
||||
|
||||
goog.require('goog.structs');
|
||||
goog.require('goog.structs.Collection');
|
||||
goog.require('goog.structs.Map');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A set that can contain both primitives and objects. Adding and removing
|
||||
* elements is O(1). Primitives are treated as identical if they have the same
|
||||
* type and convert to the same string. Objects are treated as identical only
|
||||
* if they are references to the same object. WARNING: A goog.structs.Set can
|
||||
* contain both 1 and (new Number(1)), because they are not the same. WARNING:
|
||||
* Adding (new Number(1)) twice will yield two distinct elements, because they
|
||||
* are two different objects. WARNING: Any object that is added to a
|
||||
* goog.structs.Set will be modified! Because goog.getUid() is used to
|
||||
* identify objects, every object in the set will be mutated.
|
||||
* @param {Array<T>|Object<?,T>=} opt_values Initial values to start with.
|
||||
* @constructor
|
||||
* @implements {goog.structs.Collection<T>}
|
||||
* @final
|
||||
* @template T
|
||||
*/
|
||||
goog.structs.Set = function(opt_values) {
|
||||
this.map_ = new goog.structs.Map;
|
||||
if (opt_values) {
|
||||
this.addAll(opt_values);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Obtains a unique key for an element of the set. Primitives will yield the
|
||||
* same key if they have the same type and convert to the same string. Object
|
||||
* references will yield the same key only if they refer to the same object.
|
||||
* @param {*} val Object or primitive value to get a key for.
|
||||
* @return {string} A unique key for this value/object.
|
||||
* @private
|
||||
*/
|
||||
goog.structs.Set.getKey_ = function(val) {
|
||||
var type = typeof val;
|
||||
if (type == 'object' && val || type == 'function') {
|
||||
return 'o' + goog.getUid(/** @type {Object} */ (val));
|
||||
} else {
|
||||
return type.substr(0, 1) + val;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {number} The number of elements in the set.
|
||||
* @override
|
||||
*/
|
||||
goog.structs.Set.prototype.getCount = function() {
|
||||
return this.map_.getCount();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Add a primitive or an object to the set.
|
||||
* @param {T} element The primitive or object to add.
|
||||
* @override
|
||||
*/
|
||||
goog.structs.Set.prototype.add = function(element) {
|
||||
this.map_.set(goog.structs.Set.getKey_(element), element);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Adds all the values in the given collection to this set.
|
||||
* @param {Array<T>|goog.structs.Collection<T>|Object<?,T>} col A collection
|
||||
* containing the elements to add.
|
||||
*/
|
||||
goog.structs.Set.prototype.addAll = function(col) {
|
||||
var values = goog.structs.getValues(col);
|
||||
var l = values.length;
|
||||
for (var i = 0; i < l; i++) {
|
||||
this.add(values[i]);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Removes all values in the given collection from this set.
|
||||
* @param {Array<T>|goog.structs.Collection<T>|Object<?,T>} col A collection
|
||||
* containing the elements to remove.
|
||||
*/
|
||||
goog.structs.Set.prototype.removeAll = function(col) {
|
||||
var values = goog.structs.getValues(col);
|
||||
var l = values.length;
|
||||
for (var i = 0; i < l; i++) {
|
||||
this.remove(values[i]);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Removes the given element from this set.
|
||||
* @param {T} element The primitive or object to remove.
|
||||
* @return {boolean} Whether the element was found and removed.
|
||||
* @override
|
||||
*/
|
||||
goog.structs.Set.prototype.remove = function(element) {
|
||||
return this.map_.remove(goog.structs.Set.getKey_(element));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Removes all elements from this set.
|
||||
*/
|
||||
goog.structs.Set.prototype.clear = function() {
|
||||
this.map_.clear();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Tests whether this set is empty.
|
||||
* @return {boolean} True if there are no elements in this set.
|
||||
*/
|
||||
goog.structs.Set.prototype.isEmpty = function() {
|
||||
return this.map_.isEmpty();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Tests whether this set contains the given element.
|
||||
* @param {T} element The primitive or object to test for.
|
||||
* @return {boolean} True if this set contains the given element.
|
||||
* @override
|
||||
*/
|
||||
goog.structs.Set.prototype.contains = function(element) {
|
||||
return this.map_.containsKey(goog.structs.Set.getKey_(element));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Tests whether this set contains all the values in a given collection.
|
||||
* Repeated elements in the collection are ignored, e.g. (new
|
||||
* goog.structs.Set([1, 2])).containsAll([1, 1]) is True.
|
||||
* @param {goog.structs.Collection<T>|Object} col A collection-like object.
|
||||
* @return {boolean} True if the set contains all elements.
|
||||
*/
|
||||
goog.structs.Set.prototype.containsAll = function(col) {
|
||||
return goog.structs.every(col, this.contains, this);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Finds all values that are present in both this set and the given collection.
|
||||
* @param {Array<S>|Object<?,S>} col A collection.
|
||||
* @return {!goog.structs.Set<T|S>} A new set containing all the values
|
||||
* (primitives or objects) present in both this set and the given
|
||||
* collection.
|
||||
* @template S
|
||||
*/
|
||||
goog.structs.Set.prototype.intersection = function(col) {
|
||||
var result = new goog.structs.Set();
|
||||
|
||||
var values = goog.structs.getValues(col);
|
||||
for (var i = 0; i < values.length; i++) {
|
||||
var value = values[i];
|
||||
if (this.contains(value)) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Finds all values that are present in this set and not in the given
|
||||
* collection.
|
||||
* @param {Array<T>|goog.structs.Collection<T>|Object<?,T>} col A collection.
|
||||
* @return {!goog.structs.Set} A new set containing all the values
|
||||
* (primitives or objects) present in this set but not in the given
|
||||
* collection.
|
||||
*/
|
||||
goog.structs.Set.prototype.difference = function(col) {
|
||||
var result = this.clone();
|
||||
result.removeAll(col);
|
||||
return result;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns an array containing all the elements in this set.
|
||||
* @return {!Array<T>} An array containing all the elements in this set.
|
||||
*/
|
||||
goog.structs.Set.prototype.getValues = function() {
|
||||
return this.map_.getValues();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates a shallow clone of this set.
|
||||
* @return {!goog.structs.Set<T>} A new set containing all the same elements as
|
||||
* this set.
|
||||
*/
|
||||
goog.structs.Set.prototype.clone = function() {
|
||||
return new goog.structs.Set(this);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Tests whether the given collection consists of the same elements as this set,
|
||||
* regardless of order, without repetition. Primitives are treated as equal if
|
||||
* they have the same type and convert to the same string; objects are treated
|
||||
* as equal if they are references to the same object. This operation is O(n).
|
||||
* @param {goog.structs.Collection<T>|Object} col A collection.
|
||||
* @return {boolean} True if the given collection consists of the same elements
|
||||
* as this set, regardless of order, without repetition.
|
||||
*/
|
||||
goog.structs.Set.prototype.equals = function(col) {
|
||||
return this.getCount() == goog.structs.getCount(col) && this.isSubsetOf(col);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Tests whether the given collection contains all the elements in this set.
|
||||
* Primitives are treated as equal if they have the same type and convert to the
|
||||
* same string; objects are treated as equal if they are references to the same
|
||||
* object. This operation is O(n).
|
||||
* @param {goog.structs.Collection<T>|Object} col A collection.
|
||||
* @return {boolean} True if this set is a subset of the given collection.
|
||||
*/
|
||||
goog.structs.Set.prototype.isSubsetOf = function(col) {
|
||||
var colCount = goog.structs.getCount(col);
|
||||
if (this.getCount() > colCount) {
|
||||
return false;
|
||||
}
|
||||
// TODO(user) Find the minimal collection size where the conversion makes
|
||||
// the contains() method faster.
|
||||
if (!(col instanceof goog.structs.Set) && colCount > 5) {
|
||||
// Convert to a goog.structs.Set so that goog.structs.contains runs in
|
||||
// O(1) time instead of O(n) time.
|
||||
col = new goog.structs.Set(col);
|
||||
}
|
||||
return goog.structs.every(this, function(value) {
|
||||
return goog.structs.contains(col, value);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns an iterator that iterates over the elements in this set.
|
||||
* @param {boolean=} opt_keys This argument is ignored.
|
||||
* @return {!goog.iter.Iterator} An iterator over the elements in this set.
|
||||
*/
|
||||
goog.structs.Set.prototype.__iterator__ = function(opt_keys) {
|
||||
return this.map_.__iterator__(false);
|
||||
};
|
||||
@@ -0,0 +1,267 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
Copyright 2009 The Closure Library Authors. All Rights Reserved.
|
||||
|
||||
Use of this source code is governed by the Apache License, Version 2.0.
|
||||
See the COPYING file for details.
|
||||
-->
|
||||
<!--
|
||||
-->
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<title>Closure Performance Tests - goog.ui.Set vs goog.ui.StringSet</title>
|
||||
<link rel="stylesheet" type="text/css" href="../testing/performancetable.css"/>
|
||||
<script src="../base.js"></script>
|
||||
<script>
|
||||
goog.require('goog.functions');
|
||||
goog.require('goog.string');
|
||||
goog.require('goog.structs.Set');
|
||||
goog.require('goog.structs.StringSet');
|
||||
goog.require('goog.testing.PerformanceTable');
|
||||
goog.require('goog.testing.PropertyReplacer');
|
||||
goog.require('goog.testing.jsunit');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<h1>goog.ui.Set and goog.ui.StringSet Performance Tests</h1>
|
||||
<p>
|
||||
<strong>User-agent:</strong>
|
||||
<script>document.write(navigator.userAgent);</script>
|
||||
</p>
|
||||
<div id="perfTable"></div>
|
||||
<hr>
|
||||
|
||||
<script>
|
||||
var table = new goog.testing.PerformanceTable(
|
||||
goog.dom.getElement('perfTable'));
|
||||
|
||||
// Number of operations to measure in each table line.
|
||||
var OPS_COUNT = 100000;
|
||||
|
||||
var stubs = new goog.testing.PropertyReplacer();
|
||||
|
||||
function tearDown() {
|
||||
stubs.reset();
|
||||
}
|
||||
|
||||
function testCreateSetFromArrayWithoutRepetition() {
|
||||
var values = []
|
||||
for (var i = 0; i < OPS_COUNT; i++) {
|
||||
values.push(i);
|
||||
}
|
||||
|
||||
table.run(function() {
|
||||
var s = new goog.structs.StringSet(values);
|
||||
}, 'Create string set from number array without repetition');
|
||||
|
||||
values = []
|
||||
for (var i = 0; i < OPS_COUNT; i++) {
|
||||
values.push(String(i));
|
||||
}
|
||||
|
||||
table.run(function() {
|
||||
var s = new goog.structs.StringSet(values);
|
||||
}, 'Create string set from string array without repetition');
|
||||
}
|
||||
|
||||
function testCreateSetWithoutRepetition() {
|
||||
table.run(function() {
|
||||
var s = new goog.structs.Set();
|
||||
for (var i = 0; i < OPS_COUNT; i++) {
|
||||
s.add(i);
|
||||
}
|
||||
}, 'Add elements to set without repetition');
|
||||
|
||||
table.run(function() {
|
||||
var s = new goog.structs.StringSet();
|
||||
for (var i = 0; i < OPS_COUNT; i++) {
|
||||
s.add(i);
|
||||
}
|
||||
}, 'Add elements to string set without repetition');
|
||||
|
||||
stubs.replace(goog.structs.StringSet, 'encode_', goog.functions.identity);
|
||||
stubs.replace(goog.structs.StringSet, 'decode_', goog.functions.identity);
|
||||
|
||||
table.run(function() {
|
||||
var s = new goog.structs.StringSet();
|
||||
for (var i = 0; i < OPS_COUNT; i++) {
|
||||
s.add(i);
|
||||
}
|
||||
}, 'Add elements to string set without repetition and escaping');
|
||||
}
|
||||
|
||||
function testCreateSetWithRepetition() {
|
||||
table.run(function() {
|
||||
var s = new goog.structs.Set();
|
||||
for (var n = 0; n < 10 ; n++) {
|
||||
for (var i = 0; i < OPS_COUNT / 10; i++) {
|
||||
s.add(i);
|
||||
}
|
||||
}
|
||||
}, 'Add elements to set with repetition');
|
||||
|
||||
table.run(function() {
|
||||
var s = new goog.structs.StringSet();
|
||||
for (var n = 0; n < 10; n++) {
|
||||
for (var i = 0; i < OPS_COUNT / 10; i++) {
|
||||
s.add(i);
|
||||
}
|
||||
}
|
||||
}, 'Add elements to string set with repetition');
|
||||
}
|
||||
|
||||
function testGetCount() {
|
||||
var bigSet = new goog.structs.Set;
|
||||
var bigStringSet = new goog.structs.StringSet;
|
||||
for (var i = 0; i < OPS_COUNT; i++) {
|
||||
bigSet.add(i);
|
||||
bigStringSet.add(i);
|
||||
}
|
||||
|
||||
table.run(function() {
|
||||
bigSet.getCount();
|
||||
}, 'Count the number of elements in a set');
|
||||
|
||||
table.run(function() {
|
||||
bigStringSet.getCount();
|
||||
}, 'Count the number of elements in a string set');
|
||||
}
|
||||
|
||||
function testGetValues() {
|
||||
var bigSet = new goog.structs.Set;
|
||||
var bigStringSet = new goog.structs.StringSet;
|
||||
for (var i = 0; i < OPS_COUNT; i++) {
|
||||
bigSet.add(i);
|
||||
bigStringSet.add(i);
|
||||
}
|
||||
|
||||
table.run(function() {
|
||||
bigSet.getValues();
|
||||
}, 'Convert a set to array');
|
||||
|
||||
table.run(function() {
|
||||
bigStringSet.getValues();
|
||||
}, 'Convert a string set to array');
|
||||
}
|
||||
|
||||
function testForEach() {
|
||||
var bigSet = new goog.structs.Set;
|
||||
var bigStringSet = new goog.structs.StringSet;
|
||||
for (var i = 0; i < OPS_COUNT; i++) {
|
||||
bigSet.add(i);
|
||||
bigStringSet.add(i);
|
||||
}
|
||||
|
||||
table.run(function() {
|
||||
goog.structs.forEach(bigSet, goog.nullFunction);
|
||||
}, 'Iterate over set with forEach');
|
||||
|
||||
table.run(function() {
|
||||
goog.structs.forEach(bigStringSet, goog.nullFunction);
|
||||
}, 'Iterate over string set with forEach');
|
||||
}
|
||||
|
||||
function testForEachWithLargeKeys() {
|
||||
var bigSet = new goog.structs.Set;
|
||||
var bigStringSet = new goog.structs.StringSet;
|
||||
for (var i = 0; i < OPS_COUNT / 100; i++) {
|
||||
bigSet.add(goog.string.repeat(String(i), 1000));
|
||||
bigStringSet.add(goog.string.repeat(String(i), 1000));
|
||||
}
|
||||
|
||||
table.run(function() {
|
||||
for (var i = 0; i < 100; i++) {
|
||||
goog.structs.forEach(bigSet, goog.nullFunction);
|
||||
}
|
||||
}, 'Iterate over set of large strings with forEach');
|
||||
|
||||
table.run(function() {
|
||||
for (var i = 0; i < 100; i++) {
|
||||
goog.structs.forEach(bigStringSet, goog.nullFunction);
|
||||
}
|
||||
}, 'Iterate over string set of large strings with forEach');
|
||||
}
|
||||
|
||||
function testAddRemove() {
|
||||
table.run(function() {
|
||||
var s = new goog.structs.Set();
|
||||
for (var i = 0; i < OPS_COUNT / 2; i++) {
|
||||
s.add(i);
|
||||
}
|
||||
for (var i = 0; i < OPS_COUNT / 2; i++) {
|
||||
s.remove(i);
|
||||
}
|
||||
}, 'Add then remove elements from set');
|
||||
|
||||
table.run(function() {
|
||||
var s = new goog.structs.StringSet();
|
||||
for (var i = 0; i < OPS_COUNT / 2; i++) {
|
||||
s.add(i);
|
||||
}
|
||||
for (var i = 0; i < OPS_COUNT / 2; i++) {
|
||||
s.remove(i);
|
||||
}
|
||||
}, 'Add then remove elements from string set');
|
||||
}
|
||||
|
||||
function testContains() {
|
||||
var bigSet = new goog.structs.Set;
|
||||
var bigStringSet = new goog.structs.StringSet;
|
||||
var arr = [];
|
||||
for (var i = 0; i < OPS_COUNT; i++) {
|
||||
bigSet.add(i);
|
||||
bigStringSet.add(i);
|
||||
arr.push(i);
|
||||
}
|
||||
|
||||
table.run(function() {
|
||||
for (var i = 0; i < OPS_COUNT; i++) {
|
||||
bigSet.contains(i);
|
||||
}
|
||||
}, 'Membership check for each element of set');
|
||||
|
||||
table.run(function() {
|
||||
for (var i = 0; i < OPS_COUNT; i++) {
|
||||
bigStringSet.contains(i);
|
||||
}
|
||||
}, 'Membership check for each element of string set with contains');
|
||||
|
||||
table.run(function() {
|
||||
bigStringSet.containsArray(arr);
|
||||
}, 'Membership check for each element of string set with containsArray');
|
||||
|
||||
stubs.replace(goog.structs.StringSet, 'encode_', goog.functions.identity);
|
||||
stubs.replace(goog.structs.StringSet, 'decode_', goog.functions.identity);
|
||||
|
||||
table.run(function() {
|
||||
for (var i = 0; i < OPS_COUNT; i++) {
|
||||
bigStringSet.contains(i);
|
||||
}
|
||||
}, 'Membership check for each element of string set without escaping');
|
||||
}
|
||||
|
||||
function testEquals() {
|
||||
table.run(function() {
|
||||
var s1 = new goog.structs.Set();
|
||||
var s2 = new goog.structs.Set();
|
||||
for (var i = 0; i < OPS_COUNT / 4; i++) {
|
||||
s1.add(i);
|
||||
s2.add(i);
|
||||
}
|
||||
s1.equals(s2);
|
||||
}, 'Create then compare two sets');
|
||||
|
||||
table.run(function() {
|
||||
var s1 = new goog.structs.StringSet();
|
||||
var s2 = new goog.structs.StringSet();
|
||||
for (var i = 0; i < OPS_COUNT / 4; i++) {
|
||||
s1.add(i);
|
||||
s2.add(i);
|
||||
}
|
||||
s1.equals(s2);
|
||||
}, 'Create then compare two string sets');
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,22 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
Copyright 2006 The Closure Library Authors. All Rights Reserved.
|
||||
|
||||
Use of this source code is governed by the Apache License, Version 2.0.
|
||||
See the COPYING file for details.
|
||||
-->
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<title>
|
||||
Closure Unit Tests - goog.structs.Set
|
||||
</title>
|
||||
<script src="../base.js">
|
||||
</script>
|
||||
<script>
|
||||
goog.require('goog.structs.SetTest');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,579 @@
|
||||
// Copyright 2006 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.
|
||||
|
||||
goog.provide('goog.structs.SetTest');
|
||||
goog.setTestOnly('goog.structs.SetTest');
|
||||
|
||||
goog.require('goog.iter');
|
||||
goog.require('goog.structs');
|
||||
goog.require('goog.structs.Set');
|
||||
goog.require('goog.testing.jsunit');
|
||||
|
||||
var Set = goog.structs.Set;
|
||||
|
||||
function stringifySet(s) {
|
||||
return goog.structs.getValues(s).join('');
|
||||
}
|
||||
|
||||
function testGetCount() {
|
||||
var s = new Set;
|
||||
var a = new String('a'); s.add(a);
|
||||
var b = new String('b'); s.add(b);
|
||||
var c = new String('c'); s.add(c);
|
||||
assertEquals('count, should be 3', s.getCount(), 3);
|
||||
var d = new String('d'); s.add(d);
|
||||
assertEquals('count, should be 4', s.getCount(), 4);
|
||||
s.remove(d);
|
||||
assertEquals('count, should be 3', s.getCount(), 3);
|
||||
|
||||
s = new Set;
|
||||
s.add('a');
|
||||
s.add('b');
|
||||
s.add('c');
|
||||
assertEquals('count, should be 3', s.getCount(), 3);
|
||||
s.add('d');
|
||||
assertEquals('count, should be 4', s.getCount(), 4);
|
||||
s.remove('d');
|
||||
assertEquals('count, should be 3', s.getCount(), 3);
|
||||
}
|
||||
|
||||
function testGetValues() {
|
||||
var s = new Set;
|
||||
var a = new String('a'); s.add(a);
|
||||
var b = new String('b'); s.add(b);
|
||||
var c = new String('c'); s.add(c);
|
||||
var d = new String('d'); s.add(d);
|
||||
assertEquals(s.getValues().join(''), 'abcd');
|
||||
|
||||
var s = new Set;
|
||||
s.add('a');
|
||||
s.add('b');
|
||||
s.add('c');
|
||||
s.add('d');
|
||||
assertEquals(s.getValues().join(''), 'abcd');
|
||||
}
|
||||
|
||||
function testContains() {
|
||||
var s = new Set;
|
||||
var a = new String('a'); s.add(a);
|
||||
var b = new String('b'); s.add(b);
|
||||
var c = new String('c'); s.add(c);
|
||||
var d = new String('d'); s.add(d);
|
||||
var e = new String('e');
|
||||
|
||||
assertTrue("contains, Should contain 'a'", s.contains(a));
|
||||
assertFalse("contains, Should not contain 'e'", s.contains(e));
|
||||
|
||||
s = new Set;
|
||||
s.add('a');
|
||||
s.add('b');
|
||||
s.add('c');
|
||||
s.add('d');
|
||||
|
||||
assertTrue("contains, Should contain 'a'", s.contains('a'));
|
||||
assertFalse("contains, Should not contain 'e'", s.contains('e'));
|
||||
}
|
||||
|
||||
function testContainsFunctionValue() {
|
||||
var s = new Set;
|
||||
|
||||
var fn1 = function() {};
|
||||
|
||||
assertFalse(s.contains(fn1));
|
||||
s.add(fn1);
|
||||
assertTrue(s.contains(fn1));
|
||||
|
||||
var fn2 = function() {};
|
||||
|
||||
assertFalse(s.contains(fn2));
|
||||
s.add(fn2);
|
||||
assertTrue(s.contains(fn2));
|
||||
|
||||
assertEquals(s.getCount(), 2);
|
||||
}
|
||||
|
||||
function testContainsAll() {
|
||||
var set = new Set([1, 2, 3]);
|
||||
|
||||
assertTrue('{1, 2, 3} contains []', set.containsAll([]));
|
||||
assertTrue('{1, 2, 3} contains [1]', set.containsAll([1]));
|
||||
assertTrue('{1, 2, 3} contains [1, 1]', set.containsAll([1, 1]));
|
||||
assertTrue('{1, 2, 3} contains [3, 2, 1]', set.containsAll([3, 2, 1]));
|
||||
assertFalse("{1, 2, 3} doesn't contain [4]", set.containsAll([4]));
|
||||
assertFalse("{1, 2, 3} doesn't contain [1, 4]", set.containsAll([1, 4]));
|
||||
|
||||
assertTrue('{1, 2, 3} contains {a: 1}', set.containsAll({a: 1}));
|
||||
assertFalse("{1, 2, 3} doesn't contain {a: 4}", set.containsAll({a: 4}));
|
||||
|
||||
assertTrue('{1, 2, 3} contains {1}', set.containsAll(new Set([1])));
|
||||
assertFalse("{1, 2, 3} doesn't contain {4}", set.containsAll(new Set([4])));
|
||||
}
|
||||
|
||||
function testIntersection() {
|
||||
var emptySet = new Set;
|
||||
|
||||
assertTrue('intersection of empty set and [] should be empty',
|
||||
emptySet.intersection([]).isEmpty());
|
||||
assertIntersection('intersection of 2 empty sets should be empty',
|
||||
emptySet, new Set(), new Set());
|
||||
|
||||
var abcdSet = new Set();
|
||||
abcdSet.add('a');
|
||||
abcdSet.add('b');
|
||||
abcdSet.add('c');
|
||||
abcdSet.add('d');
|
||||
|
||||
assertTrue('intersection of populated set and [] should be empty',
|
||||
abcdSet.intersection([]).isEmpty());
|
||||
assertIntersection(
|
||||
'intersection of populated set and empty set should be empty',
|
||||
abcdSet, new Set(), new Set());
|
||||
|
||||
var bcSet = new Set(['b', 'c']);
|
||||
assertIntersection('intersection of [a,b,c,d] and [b,c]',
|
||||
abcdSet, bcSet, bcSet);
|
||||
|
||||
var bceSet = new Set(['b', 'c', 'e']);
|
||||
assertIntersection('intersection of [a,b,c,d] and [b,c,e]',
|
||||
abcdSet, bceSet, bcSet);
|
||||
}
|
||||
|
||||
function testDifference() {
|
||||
var emptySet = new Set;
|
||||
|
||||
assertTrue('difference of empty set and [] should be empty',
|
||||
emptySet.difference([]).isEmpty());
|
||||
assertTrue('difference of 2 empty sets should be empty',
|
||||
emptySet.difference(new Set()).isEmpty());
|
||||
|
||||
var abcdSet = new Set(['a', 'b', 'c', 'd']);
|
||||
|
||||
assertTrue('difference of populated set and [] should be the populated set',
|
||||
abcdSet.difference([]).equals(abcdSet));
|
||||
assertTrue(
|
||||
'difference of populated set and empty set should be the populated set',
|
||||
abcdSet.difference(new Set()).equals(abcdSet));
|
||||
assertTrue('difference of two identical sets should be the empty set',
|
||||
abcdSet.difference(abcdSet).equals(new Set()));
|
||||
|
||||
var bcSet = new Set(['b', 'c']);
|
||||
assertTrue('difference of [a,b,c,d] and [b,c] shoule be [a,d]',
|
||||
abcdSet.difference(bcSet).equals(new Set(['a', 'd'])));
|
||||
assertTrue('difference of [b,c] and [a,b,c,d] should be the empty set',
|
||||
bcSet.difference(abcdSet).equals(new Set()));
|
||||
|
||||
var xyzSet = new Set(['x', 'y', 'z']);
|
||||
assertTrue('difference of [a,b,c,d] and [x,y,z] should be the [a,b,c,d]',
|
||||
abcdSet.difference(xyzSet).equals(abcdSet));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Helper function to assert intersection is commutative.
|
||||
*/
|
||||
function assertIntersection(msg, set1, set2, expectedIntersection) {
|
||||
assertTrue(msg + ': set1->set2',
|
||||
set1.intersection(set2).equals(expectedIntersection));
|
||||
assertTrue(msg + ': set2->set1',
|
||||
set2.intersection(set1).equals(expectedIntersection));
|
||||
}
|
||||
|
||||
function testRemoveAll() {
|
||||
assertRemoveAll('removeAll of empty set from empty set', [], [], []);
|
||||
assertRemoveAll('removeAll of empty set from populated set',
|
||||
['a', 'b', 'c', 'd'], [], ['a', 'b', 'c', 'd']);
|
||||
assertRemoveAll('removeAll of [a,d] from [a,b,c,d]',
|
||||
['a', 'b', 'c', 'd'], ['a', 'd'], ['b', 'c']);
|
||||
assertRemoveAll('removeAll of [b,c] from [a,b,c,d]',
|
||||
['a', 'b', 'c', 'd'], ['b', 'c'], ['a', 'd']);
|
||||
assertRemoveAll('removeAll of [b,c,e] from [a,b,c,d]',
|
||||
['a', 'b', 'c', 'd'], ['b', 'c', 'e'], ['a', 'd']);
|
||||
assertRemoveAll('removeAll of [a,b,c,d] from [a,d]',
|
||||
['a', 'd'], ['a', 'b', 'c', 'd'], []);
|
||||
assertRemoveAll('removeAll of [a,b,c,d] from [b,c]',
|
||||
['b', 'c'], ['a', 'b', 'c', 'd'], []);
|
||||
assertRemoveAll('removeAll of [a,b,c,d] from [b,c,e]',
|
||||
['b', 'c', 'e'], ['a', 'b', 'c', 'd'], ['e']);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Helper function to test removeAll.
|
||||
*/
|
||||
function assertRemoveAll(msg, elements1, elements2, expectedResult) {
|
||||
var set1 = new Set(elements1);
|
||||
var set2 = new Set(elements2);
|
||||
set1.removeAll(set2);
|
||||
|
||||
assertTrue(msg + ': set1 count increased after removeAll',
|
||||
elements1.length >= set1.getCount());
|
||||
assertEquals(msg + ': set2 count changed after removeAll',
|
||||
elements2.length, set2.getCount());
|
||||
assertTrue(msg + ': wrong set1 after removeAll', set1.equals(expectedResult));
|
||||
assertIntersection(msg + ': non-empty intersection after removeAll',
|
||||
set1, set2, []);
|
||||
}
|
||||
|
||||
function testAdd() {
|
||||
var s = new Set;
|
||||
var a = new String('a');
|
||||
var b = new String('b');
|
||||
s.add(a);
|
||||
assertTrue(s.contains(a));
|
||||
s.add(b);
|
||||
assertTrue(s.contains(b));
|
||||
|
||||
s = new Set;
|
||||
s.add('a');
|
||||
assertTrue(s.contains('a'));
|
||||
s.add('b');
|
||||
assertTrue(s.contains('b'));
|
||||
s.add(null);
|
||||
assertTrue('contains null', s.contains(null));
|
||||
assertFalse('does not contain "null"', s.contains('null'));
|
||||
}
|
||||
|
||||
|
||||
function testClear() {
|
||||
var s = new Set;
|
||||
var a = new String('a'); s.add(a);
|
||||
var b = new String('b'); s.add(b);
|
||||
var c = new String('c'); s.add(c);
|
||||
var d = new String('d'); s.add(d);
|
||||
s.clear();
|
||||
assertTrue('cleared so it should be empty', s.isEmpty());
|
||||
assertTrue("cleared so it should not contain 'a' key", !s.contains(a));
|
||||
|
||||
s = new Set;
|
||||
s.add('a');
|
||||
s.add('b');
|
||||
s.add('c');
|
||||
s.add('d');
|
||||
s.clear();
|
||||
assertTrue('cleared so it should be empty', s.isEmpty());
|
||||
assertTrue("cleared so it should not contain 'a' key", !s.contains('a'));
|
||||
}
|
||||
|
||||
|
||||
function testAddAll() {
|
||||
var s = new Set;
|
||||
var a = new String('a');
|
||||
var b = new String('b');
|
||||
var c = new String('c');
|
||||
var d = new String('d');
|
||||
s.addAll([a, b, c, d]);
|
||||
assertTrue('addAll so it should not be empty', !s.isEmpty());
|
||||
assertTrue("addAll so it should contain 'c' key", s.contains(c));
|
||||
|
||||
var s2 = new Set;
|
||||
s2.addAll(s);
|
||||
assertTrue('addAll so it should not be empty', !s2.isEmpty());
|
||||
assertTrue("addAll so it should contain 'c' key", s2.contains(c));
|
||||
|
||||
|
||||
s = new Set;
|
||||
s.addAll(['a', 'b', 'c', 'd']);
|
||||
assertTrue('addAll so it should not be empty', !s.isEmpty());
|
||||
assertTrue("addAll so it should contain 'c' key", s.contains('c'));
|
||||
|
||||
s2 = new Set;
|
||||
s2.addAll(s);
|
||||
assertTrue('addAll so it should not be empty', !s2.isEmpty());
|
||||
assertTrue("addAll so it should contain 'c' key", s2.contains('c'));
|
||||
}
|
||||
|
||||
|
||||
function testConstructor() {
|
||||
var s = new Set;
|
||||
var a = new String('a'); s.add(a);
|
||||
var b = new String('b'); s.add(b);
|
||||
var c = new String('c'); s.add(c);
|
||||
var d = new String('d'); s.add(d);
|
||||
var s2 = new Set(s);
|
||||
assertFalse('constr with Set so it should not be empty', s2.isEmpty());
|
||||
assertTrue('constr with Set so it should contain c', s2.contains(c));
|
||||
|
||||
s = new Set;
|
||||
s.add('a');
|
||||
s.add('b');
|
||||
s.add('c');
|
||||
s.add('d');
|
||||
s2 = new Set(s);
|
||||
assertFalse('constr with Set so it should not be empty', s2.isEmpty());
|
||||
assertTrue('constr with Set so it should contain c', s2.contains('c'));
|
||||
}
|
||||
|
||||
|
||||
function testClone() {
|
||||
var s = new Set;
|
||||
var a = new String('a'); s.add(a);
|
||||
var b = new String('b'); s.add(b);
|
||||
var c = new String('c'); s.add(c);
|
||||
var d = new String('d'); s.add(d);
|
||||
|
||||
var s2 = s.clone();
|
||||
assertFalse('clone so it should not be empty', s2.isEmpty());
|
||||
assertTrue("clone so it should contain 'c' key", s2.contains(c));
|
||||
|
||||
var s = new Set;
|
||||
s.add('a');
|
||||
s.add('b');
|
||||
s.add('c');
|
||||
s.add('d');
|
||||
|
||||
s2 = s.clone();
|
||||
assertFalse('clone so it should not be empty', s2.isEmpty());
|
||||
assertTrue("clone so it should contain 'c' key", s2.contains('c'));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Helper method for testEquals().
|
||||
* @param {Object} a First element to use in the tests.
|
||||
* @param {Object} b Second element to use in the tests.
|
||||
* @param {Object} c Third element to use in the tests.
|
||||
* @param {Object} d Fourth element to use in the tests.
|
||||
*/
|
||||
function helperForTestEquals(a, b, c, d) {
|
||||
var s = new Set([a, b, c]);
|
||||
|
||||
assertTrue('set == itself', s.equals(s));
|
||||
assertTrue('set == same set', s.equals(new Set([a, b, c])));
|
||||
assertTrue('set == its clone', s.equals(s.clone()));
|
||||
assertTrue('set == array of same elements', s.equals([a, b, c]));
|
||||
assertTrue('set == array of same elements in different order',
|
||||
s.equals([c, b, a]));
|
||||
|
||||
assertFalse('set != empty set', s.equals(new Set));
|
||||
assertFalse('set != its subset', s.equals(new Set([a, c])));
|
||||
assertFalse('set != its superset',
|
||||
s.equals(new Set([a, b, c, d])));
|
||||
assertFalse('set != different set',
|
||||
s.equals(new Set([b, c, d])));
|
||||
assertFalse('set != its subset as array', s.equals([a, c]));
|
||||
assertFalse('set != its superset as array', s.equals([a, b, c, d]));
|
||||
assertFalse('set != different set as array', s.equals([b, c, d]));
|
||||
assertFalse('set != [a, b, c, c]', s.equals([a, b, c, c]));
|
||||
assertFalse('set != [a, b, b]', s.equals([a, b, b]));
|
||||
assertFalse('set != [a, a]', s.equals([a, a]));
|
||||
}
|
||||
|
||||
|
||||
function testEquals() {
|
||||
helperForTestEquals(1, 2, 3, 4);
|
||||
helperForTestEquals('a', 'b', 'c', 'd');
|
||||
helperForTestEquals(new String('a'), new String('b'),
|
||||
new String('c'), new String('d'));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Helper method for testIsSubsetOf().
|
||||
* @param {Object} a First element to use in the tests.
|
||||
* @param {Object} b Second element to use in the tests.
|
||||
* @param {Object} c Third element to use in the tests.
|
||||
* @param {Object} d Fourth element to use in the tests.
|
||||
*/
|
||||
function helperForTestIsSubsetOf(a, b, c, d) {
|
||||
var s = new Set([a, b, c]);
|
||||
|
||||
assertTrue('set <= itself', s.isSubsetOf(s));
|
||||
assertTrue('set <= same set',
|
||||
s.isSubsetOf(new Set([a, b, c])));
|
||||
assertTrue('set <= its clone', s.isSubsetOf(s.clone()));
|
||||
assertTrue('set <= array of same elements', s.isSubsetOf([a, b, c]));
|
||||
assertTrue('set <= array of same elements in different order',
|
||||
s.equals([c, b, a]));
|
||||
|
||||
assertTrue('set <= Set([a, b, c, d])',
|
||||
s.isSubsetOf(new Set([a, b, c, d])));
|
||||
assertTrue('set <= [a, b, c, d]', s.isSubsetOf([a, b, c, d]));
|
||||
assertTrue('set <= [a, b, c, c]', s.isSubsetOf([a, b, c, c]));
|
||||
|
||||
assertFalse('set !<= Set([a, b])',
|
||||
s.isSubsetOf(new Set([a, b])));
|
||||
assertFalse('set !<= [a, b]', s.isSubsetOf([a, b]));
|
||||
assertFalse('set !<= Set([c, d])',
|
||||
s.isSubsetOf(new Set([c, d])));
|
||||
assertFalse('set !<= [c, d]', s.isSubsetOf([c, d]));
|
||||
assertFalse('set !<= Set([a, c, d])',
|
||||
s.isSubsetOf(new Set([a, c, d])));
|
||||
assertFalse('set !<= [a, c, d]', s.isSubsetOf([a, c, d]));
|
||||
assertFalse('set !<= [a, a, b]', s.isSubsetOf([a, a, b]));
|
||||
assertFalse('set !<= [a, a, b, b]', s.isSubsetOf([a, a, b, b]));
|
||||
}
|
||||
|
||||
|
||||
function testIsSubsetOf() {
|
||||
helperForTestIsSubsetOf(1, 2, 3, 4);
|
||||
helperForTestIsSubsetOf('a', 'b', 'c', 'd');
|
||||
helperForTestIsSubsetOf(new String('a'), new String('b'),
|
||||
new String('c'), new String('d'));
|
||||
}
|
||||
|
||||
|
||||
function testForEach() {
|
||||
var s = new Set;
|
||||
var a = new String('a'); s.add(a);
|
||||
var b = new String('b'); s.add(b);
|
||||
var c = new String('c'); s.add(c);
|
||||
var d = new String('d'); s.add(d);
|
||||
var str = '';
|
||||
goog.structs.forEach(s, function(val, key, set) {
|
||||
assertUndefined(key);
|
||||
assertEquals(s, set);
|
||||
str += val;
|
||||
});
|
||||
assertEquals(str, 'abcd');
|
||||
|
||||
s = new Set;
|
||||
s.add('a');
|
||||
s.add('b');
|
||||
s.add('c');
|
||||
s.add('d');
|
||||
str = '';
|
||||
goog.structs.forEach(s, function(val, key, set) {
|
||||
assertUndefined(key);
|
||||
assertEquals(s, set);
|
||||
str += val;
|
||||
});
|
||||
assertEquals(str, 'abcd');
|
||||
}
|
||||
|
||||
|
||||
function testFilter() {
|
||||
var s = new Set;
|
||||
var a = new Number(0); s.add(a);
|
||||
var b = new Number(1); s.add(b);
|
||||
var c = new Number(2); s.add(c);
|
||||
var d = new Number(3); s.add(d);
|
||||
|
||||
var s2 = goog.structs.filter(s, function(val, key, set) {
|
||||
assertUndefined(key);
|
||||
assertEquals(s, set);
|
||||
return val > 1;
|
||||
});
|
||||
assertEquals(stringifySet(s2), '23');
|
||||
|
||||
s = new Set;
|
||||
s.add(0);
|
||||
s.add(1);
|
||||
s.add(2);
|
||||
s.add(3);
|
||||
|
||||
s2 = goog.structs.filter(s, function(val, key, set) {
|
||||
assertUndefined(key);
|
||||
assertEquals(s, set);
|
||||
return val > 1;
|
||||
});
|
||||
assertEquals(stringifySet(s2), '23');
|
||||
}
|
||||
|
||||
|
||||
function testSome() {
|
||||
var s = new Set;
|
||||
var a = new Number(0); s.add(a);
|
||||
var b = new Number(1); s.add(b);
|
||||
var c = new Number(2); s.add(c);
|
||||
var d = new Number(3); s.add(d);
|
||||
|
||||
var b = goog.structs.some(s, function(val, key, s2) {
|
||||
assertUndefined(key);
|
||||
assertEquals(s, s2);
|
||||
return val > 1;
|
||||
});
|
||||
assertTrue(b);
|
||||
var b = goog.structs.some(s, function(val, key, s2) {
|
||||
assertUndefined(key);
|
||||
assertEquals(s, s2);
|
||||
return val > 100;
|
||||
});
|
||||
assertFalse(b);
|
||||
|
||||
s = new Set;
|
||||
s.add(0);
|
||||
s.add(1);
|
||||
s.add(2);
|
||||
s.add(3);
|
||||
|
||||
b = goog.structs.some(s, function(val, key, s2) {
|
||||
assertUndefined(key);
|
||||
assertEquals(s, s2);
|
||||
return val > 1;
|
||||
});
|
||||
assertTrue(b);
|
||||
b = goog.structs.some(s, function(val, key, s2) {
|
||||
assertUndefined(key);
|
||||
assertEquals(s, s2);
|
||||
return val > 100;
|
||||
});
|
||||
assertFalse(b);
|
||||
}
|
||||
|
||||
|
||||
function testEvery() {
|
||||
var s = new Set;
|
||||
var a = new Number(0); s.add(a);
|
||||
var b = new Number(1); s.add(b);
|
||||
var c = new Number(2); s.add(c);
|
||||
var d = new Number(3); s.add(d);
|
||||
|
||||
var b = goog.structs.every(s, function(val, key, s2) {
|
||||
assertUndefined(key);
|
||||
assertEquals(s, s2);
|
||||
return val >= 0;
|
||||
});
|
||||
assertTrue(b);
|
||||
b = goog.structs.every(s, function(val, key, s2) {
|
||||
assertUndefined(key);
|
||||
assertEquals(s, s2);
|
||||
return val > 1;
|
||||
});
|
||||
assertFalse(b);
|
||||
|
||||
s = new Set;
|
||||
s.add(0);
|
||||
s.add(1);
|
||||
s.add(2);
|
||||
s.add(3);
|
||||
|
||||
b = goog.structs.every(s, function(val, key, s2) {
|
||||
assertUndefined(key);
|
||||
assertEquals(s, s2);
|
||||
return val >= 0;
|
||||
});
|
||||
assertTrue(b);
|
||||
b = goog.structs.every(s, function(val, key, s2) {
|
||||
assertUndefined(key);
|
||||
assertEquals(s, s2);
|
||||
return val > 1;
|
||||
});
|
||||
assertFalse(b);
|
||||
}
|
||||
|
||||
function testIterator() {
|
||||
var s = new Set;
|
||||
s.add(0);
|
||||
s.add(1);
|
||||
s.add(2);
|
||||
s.add(3);
|
||||
s.add(4);
|
||||
|
||||
assertEquals('01234', goog.iter.join(s, ''));
|
||||
|
||||
s.remove(1);
|
||||
s.remove(3);
|
||||
|
||||
assertEquals('024', goog.iter.join(s, ''));
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
// 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: Pool.
|
||||
*
|
||||
*
|
||||
* A generic class for handling pools of objects that is more efficient than
|
||||
* goog.structs.Pool because it doesn't maintain a list of objects that are in
|
||||
* use. See constructor comment.
|
||||
*/
|
||||
|
||||
|
||||
goog.provide('goog.structs.SimplePool');
|
||||
|
||||
goog.require('goog.Disposable');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A generic pool class. Simpler and more efficient than goog.structs.Pool
|
||||
* because it doesn't maintain a list of objects that are in use. This class
|
||||
* has constant overhead and doesn't create any additional objects as part of
|
||||
* the pool management after construction time.
|
||||
*
|
||||
* IMPORTANT: If the objects being pooled are arrays or maps that can have
|
||||
* unlimited number of properties, they need to be cleaned before being
|
||||
* returned to the pool.
|
||||
*
|
||||
* Also note that {@see goog.object.clean} actually allocates an array to clean
|
||||
* the object passed to it, so simply using this function would defy the
|
||||
* purpose of using the pool.
|
||||
*
|
||||
* @param {number} initialCount Initial number of objects to populate the free
|
||||
* pool at construction time.
|
||||
* @param {number} maxCount Maximum number of objects to keep in the free pool.
|
||||
* @constructor
|
||||
* @extends {goog.Disposable}
|
||||
* @template T
|
||||
*/
|
||||
goog.structs.SimplePool = function(initialCount, maxCount) {
|
||||
goog.Disposable.call(this);
|
||||
|
||||
/**
|
||||
* Function for overriding createObject. The avoids a common case requiring
|
||||
* subclassing this class.
|
||||
* @private {Function}
|
||||
*/
|
||||
this.createObjectFn_ = null;
|
||||
|
||||
/**
|
||||
* Function for overriding disposeObject. The avoids a common case requiring
|
||||
* subclassing this class.
|
||||
* @private {Function}
|
||||
*/
|
||||
this.disposeObjectFn_ = null;
|
||||
|
||||
/**
|
||||
* Maximum number of objects allowed
|
||||
* @private {number}
|
||||
*/
|
||||
this.maxCount_ = maxCount;
|
||||
|
||||
/**
|
||||
* Queue used to store objects that are currently in the pool and available
|
||||
* to be used.
|
||||
* @private {Array<T>}
|
||||
*/
|
||||
this.freeQueue_ = [];
|
||||
|
||||
this.createInitial_(initialCount);
|
||||
};
|
||||
goog.inherits(goog.structs.SimplePool, goog.Disposable);
|
||||
|
||||
|
||||
/**
|
||||
* Sets the {@code createObject} function which is used for creating a new
|
||||
* object in the pool.
|
||||
* @param {Function} createObjectFn Create object function which returns the
|
||||
* newly created object.
|
||||
*/
|
||||
goog.structs.SimplePool.prototype.setCreateObjectFn = function(createObjectFn) {
|
||||
this.createObjectFn_ = createObjectFn;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sets the {@code disposeObject} function which is used for disposing of an
|
||||
* object in the pool.
|
||||
* @param {Function} disposeObjectFn Dispose object function which takes the
|
||||
* object to dispose as a parameter.
|
||||
*/
|
||||
goog.structs.SimplePool.prototype.setDisposeObjectFn = function(
|
||||
disposeObjectFn) {
|
||||
this.disposeObjectFn_ = disposeObjectFn;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets an unused object from the the pool, if there is one available,
|
||||
* otherwise creates a new one.
|
||||
* @return {T} An object from the pool or a new one if necessary.
|
||||
*/
|
||||
goog.structs.SimplePool.prototype.getObject = function() {
|
||||
if (this.freeQueue_.length) {
|
||||
return this.freeQueue_.pop();
|
||||
}
|
||||
return this.createObject();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns an object to the pool so that it can be reused. If the pool is
|
||||
* already full, the object is disposed instead.
|
||||
* @param {T} obj The object to release.
|
||||
*/
|
||||
goog.structs.SimplePool.prototype.releaseObject = function(obj) {
|
||||
if (this.freeQueue_.length < this.maxCount_) {
|
||||
this.freeQueue_.push(obj);
|
||||
} else {
|
||||
this.disposeObject(obj);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Populates the pool with initialCount objects.
|
||||
* @param {number} initialCount The number of objects to add to the pool.
|
||||
* @private
|
||||
*/
|
||||
goog.structs.SimplePool.prototype.createInitial_ = function(initialCount) {
|
||||
if (initialCount > this.maxCount_) {
|
||||
throw Error('[goog.structs.SimplePool] Initial cannot be greater than max');
|
||||
}
|
||||
for (var i = 0; i < initialCount; i++) {
|
||||
this.freeQueue_.push(this.createObject());
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Should be overridden by sub-classes to return an instance of the object type
|
||||
* that is expected in the pool.
|
||||
* @return {T} The created object.
|
||||
*/
|
||||
goog.structs.SimplePool.prototype.createObject = function() {
|
||||
if (this.createObjectFn_) {
|
||||
return this.createObjectFn_();
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Should be overrideen to dispose of an object. Default implementation is to
|
||||
* remove all of the object's members, which should render it useless. Calls the
|
||||
* object's dispose method, if available.
|
||||
* @param {T} obj The object to dispose.
|
||||
*/
|
||||
goog.structs.SimplePool.prototype.disposeObject = function(obj) {
|
||||
if (this.disposeObjectFn_) {
|
||||
this.disposeObjectFn_(obj);
|
||||
} else if (goog.isObject(obj)) {
|
||||
if (goog.isFunction(obj.dispose)) {
|
||||
obj.dispose();
|
||||
} else {
|
||||
for (var i in obj) {
|
||||
delete obj[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Disposes of the pool and all objects currently held in the pool.
|
||||
* @override
|
||||
* @protected
|
||||
*/
|
||||
goog.structs.SimplePool.prototype.disposeInternal = function() {
|
||||
goog.structs.SimplePool.superClass_.disposeInternal.call(this);
|
||||
// Call disposeObject on each object held by the pool.
|
||||
var freeQueue = this.freeQueue_;
|
||||
while (freeQueue.length) {
|
||||
this.disposeObject(freeQueue.pop());
|
||||
}
|
||||
delete this.freeQueue_;
|
||||
};
|
||||
@@ -0,0 +1,405 @@
|
||||
// Copyright 2009 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 Data structure for set of strings.
|
||||
*
|
||||
*
|
||||
* This class implements a set data structure for strings. Adding and removing
|
||||
* is O(1). It doesn't contain any bloat from {@link goog.structs.Set}, i.e.
|
||||
* it isn't optimized for IE6 garbage collector (see the description of
|
||||
* {@link goog.structs.Map#keys_} for details), and it distinguishes its
|
||||
* elements by their string value not by hash code.
|
||||
* The implementation assumes that no new keys are added to Object.prototype.
|
||||
*/
|
||||
|
||||
goog.provide('goog.structs.StringSet');
|
||||
|
||||
goog.require('goog.asserts');
|
||||
goog.require('goog.iter');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Creates a set of strings.
|
||||
* @param {!Array<?>=} opt_elements Elements to add to the set. The non-string
|
||||
* items will be converted to strings, so 15 and '15' will mean the same.
|
||||
* @constructor
|
||||
* @final
|
||||
*/
|
||||
goog.structs.StringSet = function(opt_elements) {
|
||||
/**
|
||||
* An object storing the escaped elements of the set in its keys.
|
||||
* @type {!Object}
|
||||
* @private
|
||||
*/
|
||||
this.elements_ = {};
|
||||
|
||||
if (opt_elements) {
|
||||
for (var i = 0; i < opt_elements.length; i++) {
|
||||
this.elements_[goog.structs.StringSet.encode_(opt_elements[i])] = null;
|
||||
}
|
||||
}
|
||||
|
||||
goog.asserts.assertObjectPrototypeIsIntact();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Empty object. Referring to it is faster than creating a new empty object in
|
||||
* {@code goog.structs.StringSet.encode_}.
|
||||
* @const {!Object}
|
||||
* @private
|
||||
*/
|
||||
goog.structs.StringSet.EMPTY_OBJECT_ = {};
|
||||
|
||||
|
||||
/**
|
||||
* The '__proto__' and the '__count__' keys aren't enumerable in Firefox, and
|
||||
* 'toString', 'valueOf', 'constructor', etc. aren't enumerable in IE so they
|
||||
* have to be escaped before they are added to the internal object.
|
||||
* NOTE: When a new set is created, 50-80% of the CPU time is spent in encode.
|
||||
* @param {*} element The element to escape.
|
||||
* @return {*} The escaped element or the element itself if it doesn't have to
|
||||
* be escaped.
|
||||
* @private
|
||||
*/
|
||||
goog.structs.StringSet.encode_ = function(element) {
|
||||
return element in goog.structs.StringSet.EMPTY_OBJECT_ ||
|
||||
String(element).charCodeAt(0) == 32 ? ' ' + element : element;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Inverse function of {@code goog.structs.StringSet.encode_}.
|
||||
* NOTE: forEach would be 30% faster in FF if the compiler inlined decode.
|
||||
* @param {string} key The escaped element used as the key of the internal
|
||||
* object.
|
||||
* @return {string} The unescaped element.
|
||||
* @private
|
||||
*/
|
||||
goog.structs.StringSet.decode_ = function(key) {
|
||||
return key.charCodeAt(0) == 32 ? key.substr(1) : key;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Adds a single element to the set.
|
||||
* @param {*} element The element to add. It will be converted to string.
|
||||
*/
|
||||
goog.structs.StringSet.prototype.add = function(element) {
|
||||
this.elements_[goog.structs.StringSet.encode_(element)] = null;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Adds a the elements of an array to this set.
|
||||
* @param {!Array<?>} arr The array to add the elements of.
|
||||
*/
|
||||
goog.structs.StringSet.prototype.addArray = function(arr) {
|
||||
for (var i = 0; i < arr.length; i++) {
|
||||
this.elements_[goog.structs.StringSet.encode_(arr[i])] = null;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Adds the elements which are in {@code set1} but not in {@code set2} to this
|
||||
* set.
|
||||
* @param {!goog.structs.StringSet} set1 First set.
|
||||
* @param {!goog.structs.StringSet} set2 Second set.
|
||||
* @private
|
||||
*/
|
||||
goog.structs.StringSet.prototype.addDifference_ = function(set1, set2) {
|
||||
for (var key in set1.elements_) {
|
||||
if (!(key in set2.elements_)) {
|
||||
this.elements_[key] = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Adds a the elements of a set to this set.
|
||||
* @param {!goog.structs.StringSet} stringSet The set to add the elements of.
|
||||
*/
|
||||
goog.structs.StringSet.prototype.addSet = function(stringSet) {
|
||||
for (var key in stringSet.elements_) {
|
||||
this.elements_[key] = null;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Removes all elements of the set.
|
||||
*/
|
||||
goog.structs.StringSet.prototype.clear = function() {
|
||||
this.elements_ = {};
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {!goog.structs.StringSet} Clone of the set.
|
||||
*/
|
||||
goog.structs.StringSet.prototype.clone = function() {
|
||||
var ret = new goog.structs.StringSet;
|
||||
ret.addSet(this);
|
||||
return ret;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Tells if the set contains the given element.
|
||||
* @param {*} element The element to check.
|
||||
* @return {boolean} Whether it is in the set.
|
||||
*/
|
||||
goog.structs.StringSet.prototype.contains = function(element) {
|
||||
return goog.structs.StringSet.encode_(element) in this.elements_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Tells if the set contains all elements of the array.
|
||||
* @param {!Array<?>} arr The elements to check.
|
||||
* @return {boolean} Whether they are in the set.
|
||||
*/
|
||||
goog.structs.StringSet.prototype.containsArray = function(arr) {
|
||||
for (var i = 0; i < arr.length; i++) {
|
||||
if (!(goog.structs.StringSet.encode_(arr[i]) in this.elements_)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Tells if this set has the same elements as the given set.
|
||||
* @param {!goog.structs.StringSet} stringSet The other set.
|
||||
* @return {boolean} Whether they have the same elements.
|
||||
*/
|
||||
goog.structs.StringSet.prototype.equals = function(stringSet) {
|
||||
return this.isSubsetOf(stringSet) && stringSet.isSubsetOf(this);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Calls a function for each element in the set.
|
||||
* @param {function(string, undefined, !goog.structs.StringSet)} f The function
|
||||
* to call for every element. It takes the element, undefined (because sets
|
||||
* have no notion of keys), and the set.
|
||||
* @param {Object=} opt_obj The object to be used as the value of 'this'
|
||||
* within {@code f}.
|
||||
*/
|
||||
goog.structs.StringSet.prototype.forEach = function(f, opt_obj) {
|
||||
for (var key in this.elements_) {
|
||||
f.call(opt_obj, goog.structs.StringSet.decode_(key), undefined, this);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Counts the number of elements in the set in linear time.
|
||||
* NOTE: getCount is always called at most once per set instance in google3.
|
||||
* If this usage pattern won't change, the linear getCount implementation is
|
||||
* better, because
|
||||
* <li>populating a set and getting the number of elements in it takes the same
|
||||
* amount of time as keeping a count_ member up to date and getting its value;
|
||||
* <li>if getCount is not called, adding and removing elements have no overhead.
|
||||
* @return {number} The number of elements in the set.
|
||||
*/
|
||||
goog.structs.StringSet.prototype.getCount = Object.keys ?
|
||||
function() {
|
||||
return Object.keys(this.elements_).length;
|
||||
} :
|
||||
function() {
|
||||
var count = 0;
|
||||
for (var key in this.elements_) {
|
||||
count++;
|
||||
}
|
||||
return count;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Calculates the difference of two sets.
|
||||
* @param {!goog.structs.StringSet} stringSet The set to subtract from this set.
|
||||
* @return {!goog.structs.StringSet} {@code this} minus {@code stringSet}.
|
||||
*/
|
||||
goog.structs.StringSet.prototype.getDifference = function(stringSet) {
|
||||
var ret = new goog.structs.StringSet;
|
||||
ret.addDifference_(this, stringSet);
|
||||
return ret;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Calculates the intersection of this set with another set.
|
||||
* @param {!goog.structs.StringSet} stringSet The set to take the intersection
|
||||
* with.
|
||||
* @return {!goog.structs.StringSet} A new set with the common elements.
|
||||
*/
|
||||
goog.structs.StringSet.prototype.getIntersection = function(stringSet) {
|
||||
var ret = new goog.structs.StringSet;
|
||||
for (var key in this.elements_) {
|
||||
if (key in stringSet.elements_) {
|
||||
ret.elements_[key] = null;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Calculates the symmetric difference of two sets.
|
||||
* @param {!goog.structs.StringSet} stringSet The other set.
|
||||
* @return {!goog.structs.StringSet} A new set with the elements in exactly one
|
||||
* of {@code this} and {@code stringSet}.
|
||||
*/
|
||||
goog.structs.StringSet.prototype.getSymmetricDifference = function(stringSet) {
|
||||
var ret = new goog.structs.StringSet;
|
||||
ret.addDifference_(this, stringSet);
|
||||
ret.addDifference_(stringSet, this);
|
||||
return ret;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Calculates the union of this set and another set.
|
||||
* @param {!goog.structs.StringSet} stringSet The set to take the union with.
|
||||
* @return {!goog.structs.StringSet} A new set with the union of elements.
|
||||
*/
|
||||
goog.structs.StringSet.prototype.getUnion = function(stringSet) {
|
||||
var ret = this.clone();
|
||||
ret.addSet(stringSet);
|
||||
return ret;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {!Array<string>} The elements of the set.
|
||||
*/
|
||||
goog.structs.StringSet.prototype.getValues = Object.keys ?
|
||||
function() {
|
||||
// Object.keys was introduced in JavaScript 1.8.5, Array#map in 1.6.
|
||||
return Object.keys(this.elements_).map(
|
||||
goog.structs.StringSet.decode_, this);
|
||||
} :
|
||||
function() {
|
||||
var ret = [];
|
||||
for (var key in this.elements_) {
|
||||
ret.push(goog.structs.StringSet.decode_(key));
|
||||
}
|
||||
return ret;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Tells if this set and the given set are disjoint.
|
||||
* @param {!goog.structs.StringSet} stringSet The other set.
|
||||
* @return {boolean} True iff they don't have common elements.
|
||||
*/
|
||||
goog.structs.StringSet.prototype.isDisjoint = function(stringSet) {
|
||||
for (var key in this.elements_) {
|
||||
if (key in stringSet.elements_) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {boolean} Whether the set is empty.
|
||||
*/
|
||||
goog.structs.StringSet.prototype.isEmpty = function() {
|
||||
for (var key in this.elements_) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Tells if this set is the subset of the given set.
|
||||
* @param {!goog.structs.StringSet} stringSet The other set.
|
||||
* @return {boolean} Whether this set if the subset of that.
|
||||
*/
|
||||
goog.structs.StringSet.prototype.isSubsetOf = function(stringSet) {
|
||||
for (var key in this.elements_) {
|
||||
if (!(key in stringSet.elements_)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Tells if this set is the superset of the given set.
|
||||
* @param {!goog.structs.StringSet} stringSet The other set.
|
||||
* @return {boolean} Whether this set if the superset of that.
|
||||
*/
|
||||
goog.structs.StringSet.prototype.isSupersetOf = function(stringSet) {
|
||||
return stringSet.isSubsetOf(this);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Removes a single element from the set.
|
||||
* @param {*} element The element to remove.
|
||||
* @return {boolean} Whether the element was in the set.
|
||||
*/
|
||||
goog.structs.StringSet.prototype.remove = function(element) {
|
||||
var key = goog.structs.StringSet.encode_(element);
|
||||
if (key in this.elements_) {
|
||||
delete this.elements_[key];
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Removes all elements of the given array from this set.
|
||||
* @param {!Array<?>} arr The elements to remove.
|
||||
*/
|
||||
goog.structs.StringSet.prototype.removeArray = function(arr) {
|
||||
for (var i = 0; i < arr.length; i++) {
|
||||
delete this.elements_[goog.structs.StringSet.encode_(arr[i])];
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Removes all elements of the given set from this set.
|
||||
* @param {!goog.structs.StringSet} stringSet The set of elements to remove.
|
||||
*/
|
||||
goog.structs.StringSet.prototype.removeSet = function(stringSet) {
|
||||
for (var key in stringSet.elements_) {
|
||||
delete this.elements_[key];
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns an iterator that iterates over the elements in the set.
|
||||
* NOTE: creating the iterator copies the whole set so use {@link #forEach} when
|
||||
* possible.
|
||||
* @param {boolean=} opt_keys Ignored for sets.
|
||||
* @return {!goog.iter.Iterator} An iterator over the elements in the set.
|
||||
*/
|
||||
goog.structs.StringSet.prototype.__iterator__ = function(opt_keys) {
|
||||
return goog.iter.toIterator(this.getValues());
|
||||
};
|
||||
@@ -0,0 +1,24 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
Copyright 2009 The Closure Library Authors. All Rights Reserved.
|
||||
|
||||
Use of this source code is governed by the Apache License, Version 2.0.
|
||||
See the COPYING file for details.
|
||||
-->
|
||||
<!--
|
||||
-->
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<title>
|
||||
Closure Unit Tests - goog.structs.StringSet
|
||||
</title>
|
||||
<script src="../base.js">
|
||||
</script>
|
||||
<script>
|
||||
goog.require('goog.structs.StringSetTest');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,259 @@
|
||||
// Copyright 2009 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.
|
||||
|
||||
goog.provide('goog.structs.StringSetTest');
|
||||
goog.setTestOnly('goog.structs.StringSetTest');
|
||||
|
||||
goog.require('goog.array');
|
||||
goog.require('goog.iter');
|
||||
goog.require('goog.structs.StringSet');
|
||||
goog.require('goog.testing.asserts');
|
||||
goog.require('goog.testing.jsunit');
|
||||
|
||||
var TEST_VALUES = [
|
||||
'', ' ', ' ', 'true', 'null', 'undefined', '0', 'new', 'constructor',
|
||||
'prototype', '__proto__', 'set', 'hasOwnProperty', 'toString', 'valueOf'
|
||||
];
|
||||
|
||||
var TEST_VALUES_WITH_DUPLICATES = [
|
||||
'', '', ' ', ' ', 'true', true, 'null', null, 'undefined', undefined, '0', 0,
|
||||
'new', 'constructor', 'prototype', '__proto__', 'set', 'hasOwnProperty',
|
||||
'toString', 'valueOf'
|
||||
];
|
||||
|
||||
function testConstructor() {
|
||||
var empty = new goog.structs.StringSet;
|
||||
assertSameElements('elements in empty set', [], empty.getValues());
|
||||
|
||||
var s = new goog.structs.StringSet(TEST_VALUES_WITH_DUPLICATES);
|
||||
assertSameElements('duplicates are filtered out by their string value',
|
||||
TEST_VALUES, s.getValues());
|
||||
}
|
||||
|
||||
function testConstructorAssertsThatObjectPrototypeHasNoEnumerableKeys() {
|
||||
assertNotThrows(goog.structs.StringSet);
|
||||
Object.prototype.foo = 0;
|
||||
try {
|
||||
assertThrows(goog.structs.StringSet);
|
||||
} finally {
|
||||
delete Object.prototype.foo;
|
||||
}
|
||||
assertNotThrows(goog.structs.StringSet);
|
||||
}
|
||||
|
||||
function testOverridingObjectPrototypeToStringIsSafe() {
|
||||
var originalToString = Object.prototype.toString;
|
||||
Object.prototype.toString = function() {
|
||||
return 'object';
|
||||
};
|
||||
try {
|
||||
assertEquals(0, new goog.structs.StringSet().getCount());
|
||||
assertFalse(new goog.structs.StringSet().contains('toString'));
|
||||
} finally {
|
||||
Object.prototype.toString = originalToString;
|
||||
}
|
||||
}
|
||||
|
||||
function testAdd() {
|
||||
var s = new goog.structs.StringSet;
|
||||
goog.array.forEach(TEST_VALUES_WITH_DUPLICATES, s.add, s);
|
||||
assertSameElements(TEST_VALUES, s.getValues());
|
||||
}
|
||||
|
||||
function testAddArray() {
|
||||
var s = new goog.structs.StringSet;
|
||||
s.addArray(TEST_VALUES_WITH_DUPLICATES);
|
||||
assertSameElements('added elements from array', TEST_VALUES, s.getValues());
|
||||
}
|
||||
|
||||
function testAddSet() {
|
||||
var s = new goog.structs.StringSet;
|
||||
s.addSet(new goog.structs.StringSet([1, 2]));
|
||||
assertSameElements('empty set + {1, 2}', ['1', '2'], s.getValues());
|
||||
s.addSet(new goog.structs.StringSet([2, 3]));
|
||||
assertSameElements('{1, 2} + {2, 3}', ['1', '2', '3'], s.getValues());
|
||||
}
|
||||
|
||||
function testClear() {
|
||||
var s = new goog.structs.StringSet([1, 2]);
|
||||
s.clear();
|
||||
assertSameElements('cleared set', [], s.getValues());
|
||||
}
|
||||
|
||||
function testClone() {
|
||||
var s = new goog.structs.StringSet([1, 2]);
|
||||
var c = s.clone();
|
||||
assertSameElements('elements in clone', ['1', '2'], c.getValues());
|
||||
s.add(3);
|
||||
assertSameElements('changing the original set does not affect the clone',
|
||||
['1', '2'], c.getValues());
|
||||
}
|
||||
|
||||
function testContains() {
|
||||
var e = new goog.structs.StringSet;
|
||||
goog.array.forEach(TEST_VALUES, function(element) {
|
||||
assertFalse('empty set does not contain ' + element, e.contains(element));
|
||||
});
|
||||
|
||||
var s = new goog.structs.StringSet(TEST_VALUES);
|
||||
goog.array.forEach(TEST_VALUES_WITH_DUPLICATES, function(element) {
|
||||
assertTrue('s contains ' + element, s.contains(element));
|
||||
});
|
||||
assertFalse('s does not contain 42', s.contains(42));
|
||||
}
|
||||
|
||||
function testContainsArray() {
|
||||
var s = new goog.structs.StringSet(TEST_VALUES);
|
||||
assertTrue('set contains empty array', s.containsArray([]));
|
||||
assertTrue('set contains all elements of itself with some duplication',
|
||||
s.containsArray(TEST_VALUES_WITH_DUPLICATES));
|
||||
assertFalse('set does not contain 42', s.containsArray([42]));
|
||||
}
|
||||
|
||||
function testEquals() {
|
||||
var s = new goog.structs.StringSet([1, 2]);
|
||||
assertTrue('set equals to itself', s.equals(s));
|
||||
assertTrue('set equals to its clone', s.equals(s.clone()));
|
||||
assertFalse('set does not equal to its subset',
|
||||
s.equals(new goog.structs.StringSet([1])));
|
||||
assertFalse('set does not equal to its superset',
|
||||
s.equals(new goog.structs.StringSet([1, 2, 3])));
|
||||
}
|
||||
|
||||
function testForEach() {
|
||||
var s = new goog.structs.StringSet(TEST_VALUES);
|
||||
var values = [];
|
||||
var context = {};
|
||||
s.forEach(function(value, key, stringSet) {
|
||||
assertEquals('context of forEach callback', context, this);
|
||||
values.push(value);
|
||||
assertUndefined('key argument of forEach callback', key);
|
||||
assertEquals('set argument of forEach callback', s, stringSet);
|
||||
}, context);
|
||||
assertSameElements('values passed to forEach callback', TEST_VALUES, values);
|
||||
}
|
||||
|
||||
function testGetCount() {
|
||||
var empty = new goog.structs.StringSet;
|
||||
assertEquals('count(empty set)', 0, empty.getCount());
|
||||
|
||||
var s = new goog.structs.StringSet(TEST_VALUES);
|
||||
assertEquals('count(non-empty set)', TEST_VALUES.length, s.getCount());
|
||||
}
|
||||
|
||||
function testGetDifference() {
|
||||
var s1 = new goog.structs.StringSet([1, 2]);
|
||||
var s2 = new goog.structs.StringSet([2, 3]);
|
||||
assertSameElements('{1, 2} - {2, 3}', ['1'],
|
||||
s1.getDifference(s2).getValues());
|
||||
}
|
||||
|
||||
function testGetIntersection() {
|
||||
var s1 = new goog.structs.StringSet([1, 2]);
|
||||
var s2 = new goog.structs.StringSet([2, 3]);
|
||||
assertSameElements('{1, 2} * {2, 3}', ['2'],
|
||||
s1.getIntersection(s2).getValues());
|
||||
}
|
||||
|
||||
function testGetSymmetricDifference() {
|
||||
var s1 = new goog.structs.StringSet([1, 2]);
|
||||
var s2 = new goog.structs.StringSet([2, 3]);
|
||||
assertSameElements('{1, 2} sym.diff. {2, 3}', ['1', '3'],
|
||||
s1.getSymmetricDifference(s2).getValues());
|
||||
}
|
||||
|
||||
function testGetUnion() {
|
||||
var s1 = new goog.structs.StringSet([1, 2]);
|
||||
var s2 = new goog.structs.StringSet([2, 3]);
|
||||
assertSameElements('{1, 2} + {2, 3}', ['1', '2', '3'],
|
||||
s1.getUnion(s2).getValues());
|
||||
}
|
||||
|
||||
function testIsDisjoint() {
|
||||
var s = new goog.structs.StringSet;
|
||||
var s12 = new goog.structs.StringSet([1, 2]);
|
||||
var s23 = new goog.structs.StringSet([2, 3]);
|
||||
var s34 = new goog.structs.StringSet([3, 4]);
|
||||
|
||||
assertTrue('{} and {1, 2} are disjoint', s.isDisjoint(s12));
|
||||
assertTrue('{1, 2} and {3, 4} are disjoint', s12.isDisjoint(s34));
|
||||
assertFalse('{1, 2} and {2, 3} are not disjoint', s12.isDisjoint(s23));
|
||||
}
|
||||
|
||||
function testIsEmpty() {
|
||||
assertTrue('empty set', new goog.structs.StringSet().isEmpty());
|
||||
assertFalse('non-empty set', new goog.structs.StringSet(['']).isEmpty());
|
||||
}
|
||||
|
||||
function testIsSubsetOf() {
|
||||
var s1 = new goog.structs.StringSet([1]);
|
||||
var s12 = new goog.structs.StringSet([1, 2]);
|
||||
var s123 = new goog.structs.StringSet([1, 2, 3]);
|
||||
var s23 = new goog.structs.StringSet([2, 3]);
|
||||
|
||||
assertTrue('{1, 2} is subset of {1, 2}', s12.isSubsetOf(s12));
|
||||
assertTrue('{1, 2} is subset of {1, 2, 3}', s12.isSubsetOf(s123));
|
||||
assertFalse('{1, 2} is not subset of {1}', s12.isSubsetOf(s1));
|
||||
assertFalse('{1, 2} is not subset of {2, 3}', s12.isSubsetOf(s23));
|
||||
}
|
||||
|
||||
function testIsSupersetOf() {
|
||||
var s1 = new goog.structs.StringSet([1]);
|
||||
var s12 = new goog.structs.StringSet([1, 2]);
|
||||
var s123 = new goog.structs.StringSet([1, 2, 3]);
|
||||
var s23 = new goog.structs.StringSet([2, 3]);
|
||||
|
||||
assertTrue('{1, 2} is superset of {1}', s12.isSupersetOf(s1));
|
||||
assertTrue('{1, 2} is superset of {1, 2}', s12.isSupersetOf(s12));
|
||||
assertFalse('{1, 2} is not superset of {1, 2, 3}', s12.isSupersetOf(s123));
|
||||
assertFalse('{1, 2} is not superset of {2, 3}', s12.isSupersetOf(s23));
|
||||
}
|
||||
|
||||
function testRemove() {
|
||||
var n = new goog.structs.StringSet([1, 2]);
|
||||
assertFalse('3 not removed from {1, 2}', n.remove(3));
|
||||
assertSameElements('set == {1, 2}', ['1', '2'], n.getValues());
|
||||
assertTrue('2 removed from {1, 2}', n.remove(2));
|
||||
assertSameElements('set == {1}', ['1'], n.getValues());
|
||||
assertTrue('"1" removed from {1}', n.remove('1'));
|
||||
assertSameElements('set == {}', [], n.getValues());
|
||||
|
||||
var s = new goog.structs.StringSet(TEST_VALUES);
|
||||
goog.array.forEach(TEST_VALUES, s.remove, s);
|
||||
assertSameElements('all special values have been removed', [], s.getValues());
|
||||
}
|
||||
|
||||
function testRemoveArray() {
|
||||
var s = new goog.structs.StringSet(TEST_VALUES);
|
||||
s.removeArray(TEST_VALUES.slice(0, TEST_VALUES.length - 2));
|
||||
assertSameElements('removed elements from array',
|
||||
TEST_VALUES.slice(TEST_VALUES.length - 2), s.getValues());
|
||||
}
|
||||
|
||||
function testRemoveSet() {
|
||||
var s1 = new goog.structs.StringSet([1, 2]);
|
||||
var s2 = new goog.structs.StringSet([2, 3]);
|
||||
s1.removeSet(s2);
|
||||
assertSameElements('{1, 2} - {2, 3}', ['1'], s1.getValues());
|
||||
}
|
||||
|
||||
function testIterator() {
|
||||
var s = new goog.structs.StringSet(TEST_VALUES_WITH_DUPLICATES);
|
||||
var values = [];
|
||||
goog.iter.forEach(s, function(value) {
|
||||
values.push(value);
|
||||
});
|
||||
assertSameElements('__iterator__ takes all elements once',
|
||||
TEST_VALUES, values);
|
||||
}
|
||||
@@ -0,0 +1,354 @@
|
||||
// Copyright 2006 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 Generics method for collection-like classes and objects.
|
||||
*
|
||||
* @author arv@google.com (Erik Arvidsson)
|
||||
*
|
||||
* This file contains functions to work with collections. It supports using
|
||||
* Map, Set, Array and Object and other classes that implement collection-like
|
||||
* methods.
|
||||
*/
|
||||
|
||||
|
||||
goog.provide('goog.structs');
|
||||
|
||||
goog.require('goog.array');
|
||||
goog.require('goog.object');
|
||||
|
||||
|
||||
// We treat an object as a dictionary if it has getKeys or it is an object that
|
||||
// isn't arrayLike.
|
||||
|
||||
|
||||
/**
|
||||
* Returns the number of values in the collection-like object.
|
||||
* @param {Object} col The collection-like object.
|
||||
* @return {number} The number of values in the collection-like object.
|
||||
*/
|
||||
goog.structs.getCount = function(col) {
|
||||
if (typeof col.getCount == 'function') {
|
||||
return col.getCount();
|
||||
}
|
||||
if (goog.isArrayLike(col) || goog.isString(col)) {
|
||||
return col.length;
|
||||
}
|
||||
return goog.object.getCount(col);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the values of the collection-like object.
|
||||
* @param {Object} col The collection-like object.
|
||||
* @return {!Array<?>} The values in the collection-like object.
|
||||
*/
|
||||
goog.structs.getValues = function(col) {
|
||||
if (typeof col.getValues == 'function') {
|
||||
return col.getValues();
|
||||
}
|
||||
if (goog.isString(col)) {
|
||||
return col.split('');
|
||||
}
|
||||
if (goog.isArrayLike(col)) {
|
||||
var rv = [];
|
||||
var l = col.length;
|
||||
for (var i = 0; i < l; i++) {
|
||||
rv.push(col[i]);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
return goog.object.getValues(col);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the keys of the collection. Some collections have no notion of
|
||||
* keys/indexes and this function will return undefined in those cases.
|
||||
* @param {Object} col The collection-like object.
|
||||
* @return {!Array|undefined} The keys in the collection.
|
||||
*/
|
||||
goog.structs.getKeys = function(col) {
|
||||
if (typeof col.getKeys == 'function') {
|
||||
return col.getKeys();
|
||||
}
|
||||
// if we have getValues but no getKeys we know this is a key-less collection
|
||||
if (typeof col.getValues == 'function') {
|
||||
return undefined;
|
||||
}
|
||||
if (goog.isArrayLike(col) || goog.isString(col)) {
|
||||
var rv = [];
|
||||
var l = col.length;
|
||||
for (var i = 0; i < l; i++) {
|
||||
rv.push(i);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
return goog.object.getKeys(col);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Whether the collection contains the given value. This is O(n) and uses
|
||||
* equals (==) to test the existence.
|
||||
* @param {Object} col The collection-like object.
|
||||
* @param {*} val The value to check for.
|
||||
* @return {boolean} True if the map contains the value.
|
||||
*/
|
||||
goog.structs.contains = function(col, val) {
|
||||
if (typeof col.contains == 'function') {
|
||||
return col.contains(val);
|
||||
}
|
||||
if (typeof col.containsValue == 'function') {
|
||||
return col.containsValue(val);
|
||||
}
|
||||
if (goog.isArrayLike(col) || goog.isString(col)) {
|
||||
return goog.array.contains(/** @type {!Array<?>} */ (col), val);
|
||||
}
|
||||
return goog.object.containsValue(col, val);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Whether the collection is empty.
|
||||
* @param {Object} col The collection-like object.
|
||||
* @return {boolean} True if empty.
|
||||
*/
|
||||
goog.structs.isEmpty = function(col) {
|
||||
if (typeof col.isEmpty == 'function') {
|
||||
return col.isEmpty();
|
||||
}
|
||||
|
||||
// We do not use goog.string.isEmptyOrWhitespace because here we treat the string as
|
||||
// collection and as such even whitespace matters
|
||||
|
||||
if (goog.isArrayLike(col) || goog.isString(col)) {
|
||||
return goog.array.isEmpty(/** @type {!Array<?>} */ (col));
|
||||
}
|
||||
return goog.object.isEmpty(col);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Removes all the elements from the collection.
|
||||
* @param {Object} col The collection-like object.
|
||||
*/
|
||||
goog.structs.clear = function(col) {
|
||||
// NOTE(arv): This should not contain strings because strings are immutable
|
||||
if (typeof col.clear == 'function') {
|
||||
col.clear();
|
||||
} else if (goog.isArrayLike(col)) {
|
||||
goog.array.clear(/** @type {goog.array.ArrayLike} */ (col));
|
||||
} else {
|
||||
goog.object.clear(col);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Calls a function for each value in a collection. The function takes
|
||||
* three arguments; the value, the key and the collection.
|
||||
*
|
||||
* NOTE: This will be deprecated soon! Please use a more specific method if
|
||||
* possible, e.g. goog.array.forEach, goog.object.forEach, etc.
|
||||
*
|
||||
* @param {S} col The collection-like object.
|
||||
* @param {function(this:T,?,?,S):?} f The function to call for every value.
|
||||
* This function takes
|
||||
* 3 arguments (the value, the key or undefined if the collection has no
|
||||
* notion of keys, and the collection) and the return value is irrelevant.
|
||||
* @param {T=} opt_obj The object to be used as the value of 'this'
|
||||
* within {@code f}.
|
||||
* @template T,S
|
||||
*/
|
||||
goog.structs.forEach = function(col, f, opt_obj) {
|
||||
if (typeof col.forEach == 'function') {
|
||||
col.forEach(f, opt_obj);
|
||||
} else if (goog.isArrayLike(col) || goog.isString(col)) {
|
||||
goog.array.forEach(/** @type {!Array<?>} */ (col), f, opt_obj);
|
||||
} else {
|
||||
var keys = goog.structs.getKeys(col);
|
||||
var values = goog.structs.getValues(col);
|
||||
var l = values.length;
|
||||
for (var i = 0; i < l; i++) {
|
||||
f.call(opt_obj, values[i], keys && keys[i], col);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Calls a function for every value in the collection. When a call returns true,
|
||||
* adds the value to a new collection (Array is returned by default).
|
||||
*
|
||||
* @param {S} col The collection-like object.
|
||||
* @param {function(this:T,?,?,S):boolean} f The function to call for every
|
||||
* value. This function takes
|
||||
* 3 arguments (the value, the key or undefined if the collection has no
|
||||
* notion of keys, and the collection) and should return a Boolean. If the
|
||||
* return value is true the value is added to the result collection. If it
|
||||
* is false the value is not included.
|
||||
* @param {T=} opt_obj The object to be used as the value of 'this'
|
||||
* within {@code f}.
|
||||
* @return {!Object|!Array<?>} A new collection where the passed values are
|
||||
* present. If col is a key-less collection an array is returned. If col
|
||||
* has keys and values a plain old JS object is returned.
|
||||
* @template T,S
|
||||
*/
|
||||
goog.structs.filter = function(col, f, opt_obj) {
|
||||
if (typeof col.filter == 'function') {
|
||||
return col.filter(f, opt_obj);
|
||||
}
|
||||
if (goog.isArrayLike(col) || goog.isString(col)) {
|
||||
return goog.array.filter(/** @type {!Array<?>} */ (col), f, opt_obj);
|
||||
}
|
||||
|
||||
var rv;
|
||||
var keys = goog.structs.getKeys(col);
|
||||
var values = goog.structs.getValues(col);
|
||||
var l = values.length;
|
||||
if (keys) {
|
||||
rv = {};
|
||||
for (var i = 0; i < l; i++) {
|
||||
if (f.call(opt_obj, values[i], keys[i], col)) {
|
||||
rv[keys[i]] = values[i];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// We should not use goog.array.filter here since we want to make sure that
|
||||
// the index is undefined as well as make sure that col is passed to the
|
||||
// function.
|
||||
rv = [];
|
||||
for (var i = 0; i < l; i++) {
|
||||
if (f.call(opt_obj, values[i], undefined, col)) {
|
||||
rv.push(values[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return rv;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Calls a function for every value in the collection and adds the result into a
|
||||
* new collection (defaults to creating a new Array).
|
||||
*
|
||||
* @param {S} col The collection-like object.
|
||||
* @param {function(this:T,?,?,S):V} f The function to call for every value.
|
||||
* This function takes 3 arguments (the value, the key or undefined if the
|
||||
* collection has no notion of keys, and the collection) and should return
|
||||
* something. The result will be used as the value in the new collection.
|
||||
* @param {T=} opt_obj The object to be used as the value of 'this'
|
||||
* within {@code f}.
|
||||
* @return {!Object<V>|!Array<V>} A new collection with the new values. If
|
||||
* col is a key-less collection an array is returned. If col has keys and
|
||||
* values a plain old JS object is returned.
|
||||
* @template T,S,V
|
||||
*/
|
||||
goog.structs.map = function(col, f, opt_obj) {
|
||||
if (typeof col.map == 'function') {
|
||||
return col.map(f, opt_obj);
|
||||
}
|
||||
if (goog.isArrayLike(col) || goog.isString(col)) {
|
||||
return goog.array.map(/** @type {!Array<?>} */ (col), f, opt_obj);
|
||||
}
|
||||
|
||||
var rv;
|
||||
var keys = goog.structs.getKeys(col);
|
||||
var values = goog.structs.getValues(col);
|
||||
var l = values.length;
|
||||
if (keys) {
|
||||
rv = {};
|
||||
for (var i = 0; i < l; i++) {
|
||||
rv[keys[i]] = f.call(opt_obj, values[i], keys[i], col);
|
||||
}
|
||||
} else {
|
||||
// We should not use goog.array.map here since we want to make sure that
|
||||
// the index is undefined as well as make sure that col is passed to the
|
||||
// function.
|
||||
rv = [];
|
||||
for (var i = 0; i < l; i++) {
|
||||
rv[i] = f.call(opt_obj, values[i], undefined, col);
|
||||
}
|
||||
}
|
||||
return rv;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Calls f for each value in a collection. If any call returns true this returns
|
||||
* true (without checking the rest). If all returns false this returns false.
|
||||
*
|
||||
* @param {S} col The collection-like object.
|
||||
* @param {function(this:T,?,?,S):boolean} f The function to call for every
|
||||
* value. This function takes 3 arguments (the value, the key or undefined
|
||||
* if the collection has no notion of keys, and the collection) and should
|
||||
* return a boolean.
|
||||
* @param {T=} opt_obj The object to be used as the value of 'this'
|
||||
* within {@code f}.
|
||||
* @return {boolean} True if any value passes the test.
|
||||
* @template T,S
|
||||
*/
|
||||
goog.structs.some = function(col, f, opt_obj) {
|
||||
if (typeof col.some == 'function') {
|
||||
return col.some(f, opt_obj);
|
||||
}
|
||||
if (goog.isArrayLike(col) || goog.isString(col)) {
|
||||
return goog.array.some(/** @type {!Array<?>} */ (col), f, opt_obj);
|
||||
}
|
||||
var keys = goog.structs.getKeys(col);
|
||||
var values = goog.structs.getValues(col);
|
||||
var l = values.length;
|
||||
for (var i = 0; i < l; i++) {
|
||||
if (f.call(opt_obj, values[i], keys && keys[i], col)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Calls f for each value in a collection. If all calls return true this return
|
||||
* true this returns true. If any returns false this returns false at this point
|
||||
* and does not continue to check the remaining values.
|
||||
*
|
||||
* @param {S} col The collection-like object.
|
||||
* @param {function(this:T,?,?,S):boolean} f The function to call for every
|
||||
* value. This function takes 3 arguments (the value, the key or
|
||||
* undefined if the collection has no notion of keys, and the collection)
|
||||
* and should return a boolean.
|
||||
* @param {T=} opt_obj The object to be used as the value of 'this'
|
||||
* within {@code f}.
|
||||
* @return {boolean} True if all key-value pairs pass the test.
|
||||
* @template T,S
|
||||
*/
|
||||
goog.structs.every = function(col, f, opt_obj) {
|
||||
if (typeof col.every == 'function') {
|
||||
return col.every(f, opt_obj);
|
||||
}
|
||||
if (goog.isArrayLike(col) || goog.isString(col)) {
|
||||
return goog.array.every(/** @type {!Array<?>} */ (col), f, opt_obj);
|
||||
}
|
||||
var keys = goog.structs.getKeys(col);
|
||||
var values = goog.structs.getValues(col);
|
||||
var l = values.length;
|
||||
for (var i = 0; i < l; i++) {
|
||||
if (!f.call(opt_obj, values[i], keys && keys[i], col)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
@@ -0,0 +1,53 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
Copyright 2006 The Closure Library Authors. All Rights Reserved.
|
||||
|
||||
Use of this source code is governed by the Apache License, Version 2.0.
|
||||
See the COPYING file for details.
|
||||
-->
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<title>
|
||||
Closure Unit Tests - goog.structs
|
||||
</title>
|
||||
<script src="../base.js">
|
||||
</script>
|
||||
<script>
|
||||
goog.require('goog.structsTest');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<!-- test container with 10 elements inside, 1 hr and 1 h1 with id h1 -->
|
||||
<div id="test">
|
||||
<hr />
|
||||
<p>
|
||||
Paragraph 0
|
||||
</p>
|
||||
<p>
|
||||
Paragraph 1
|
||||
</p>
|
||||
<p>
|
||||
Paragraph 2
|
||||
</p>
|
||||
<p>
|
||||
Paragraph 3
|
||||
</p>
|
||||
<p>
|
||||
Paragraph 4
|
||||
</p>
|
||||
<p>
|
||||
Paragraph 5
|
||||
</p>
|
||||
<p>
|
||||
Paragraph 6
|
||||
</p>
|
||||
<p>
|
||||
Paragraph 7
|
||||
</p>
|
||||
<h1 id="h1">
|
||||
Header
|
||||
</h1>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,455 @@
|
||||
// Copyright 2010 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 Generic tree node data structure with arbitrary number of child
|
||||
* nodes.
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.structs.TreeNode');
|
||||
|
||||
goog.require('goog.array');
|
||||
goog.require('goog.asserts');
|
||||
goog.require('goog.structs.Node');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Generic tree node data structure with arbitrary number of child nodes.
|
||||
* It is possible to create a dynamic tree structure by overriding
|
||||
* {@link #getParent} and {@link #getChildren} in a subclass. All other getters
|
||||
* will automatically work.
|
||||
*
|
||||
* @param {KEY} key Key.
|
||||
* @param {VALUE} value Value.
|
||||
* @constructor
|
||||
* @extends {goog.structs.Node<KEY, VALUE>}
|
||||
* @template KEY, VALUE
|
||||
*/
|
||||
goog.structs.TreeNode = function(key, value) {
|
||||
goog.structs.Node.call(this, key, value);
|
||||
|
||||
/**
|
||||
* Reference to the parent node or null if it has no parent.
|
||||
* @private {goog.structs.TreeNode<KEY, VALUE>}
|
||||
*/
|
||||
this.parent_ = null;
|
||||
|
||||
/**
|
||||
* Child nodes or null in case of leaf node.
|
||||
* @private {Array<!goog.structs.TreeNode<KEY, VALUE>>}
|
||||
*/
|
||||
this.children_ = null;
|
||||
};
|
||||
goog.inherits(goog.structs.TreeNode, goog.structs.Node);
|
||||
|
||||
|
||||
/**
|
||||
* Constant for empty array to avoid unnecessary allocations.
|
||||
* @private
|
||||
*/
|
||||
goog.structs.TreeNode.EMPTY_ARRAY_ = [];
|
||||
|
||||
|
||||
/**
|
||||
* @return {!goog.structs.TreeNode} Clone of the tree node without its parent
|
||||
* and child nodes. The key and the value are copied by reference.
|
||||
* @override
|
||||
*/
|
||||
goog.structs.TreeNode.prototype.clone = function() {
|
||||
return new goog.structs.TreeNode(this.getKey(), this.getValue());
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {!goog.structs.TreeNode} Clone of the subtree with this node as root.
|
||||
*/
|
||||
goog.structs.TreeNode.prototype.deepClone = function() {
|
||||
var clone = this.clone();
|
||||
this.forEachChild(function(child) {
|
||||
clone.addChild(child.deepClone());
|
||||
});
|
||||
return clone;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {goog.structs.TreeNode<KEY, VALUE>} Parent node or null if it has no
|
||||
* parent.
|
||||
*/
|
||||
goog.structs.TreeNode.prototype.getParent = function() {
|
||||
return this.parent_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {boolean} Whether the node is a leaf node.
|
||||
*/
|
||||
goog.structs.TreeNode.prototype.isLeaf = function() {
|
||||
return !this.getChildCount();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Tells if the node is the last child of its parent. This method helps how to
|
||||
* connect the tree nodes with lines: L shapes should be used before the last
|
||||
* children and |- shapes before the rest. Schematic tree visualization:
|
||||
*
|
||||
* <pre>
|
||||
* Node1
|
||||
* |-Node2
|
||||
* | L-Node3
|
||||
* | |-Node4
|
||||
* | L-Node5
|
||||
* L-Node6
|
||||
* </pre>
|
||||
*
|
||||
* @return {boolean} Whether the node has parent and is the last child of it.
|
||||
*/
|
||||
goog.structs.TreeNode.prototype.isLastChild = function() {
|
||||
var parent = this.getParent();
|
||||
return Boolean(parent && this == goog.array.peek(parent.getChildren()));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {!Array<!goog.structs.TreeNode<KEY, VALUE>>} Immutable child nodes.
|
||||
*/
|
||||
goog.structs.TreeNode.prototype.getChildren = function() {
|
||||
return this.children_ || goog.structs.TreeNode.EMPTY_ARRAY_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the child node of this node at the given index.
|
||||
* @param {number} index Child index.
|
||||
* @return {goog.structs.TreeNode<KEY, VALUE>} The node at the given index or
|
||||
* null if not found.
|
||||
*/
|
||||
goog.structs.TreeNode.prototype.getChildAt = function(index) {
|
||||
return this.getChildren()[index] || null;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {number} The number of children.
|
||||
*/
|
||||
goog.structs.TreeNode.prototype.getChildCount = function() {
|
||||
return this.getChildren().length;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {number} The number of ancestors of the node.
|
||||
*/
|
||||
goog.structs.TreeNode.prototype.getDepth = function() {
|
||||
var depth = 0;
|
||||
var node = this;
|
||||
while (node.getParent()) {
|
||||
depth++;
|
||||
node = node.getParent();
|
||||
}
|
||||
return depth;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {!Array<!goog.structs.TreeNode<KEY, VALUE>>} All ancestor nodes in
|
||||
* bottom-up order.
|
||||
*/
|
||||
goog.structs.TreeNode.prototype.getAncestors = function() {
|
||||
var ancestors = [];
|
||||
var node = this.getParent();
|
||||
while (node) {
|
||||
ancestors.push(node);
|
||||
node = node.getParent();
|
||||
}
|
||||
return ancestors;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {!goog.structs.TreeNode<KEY, VALUE>} The root of the tree structure,
|
||||
* i.e. the farthest ancestor of the node or the node itself if it has no
|
||||
* parents.
|
||||
*/
|
||||
goog.structs.TreeNode.prototype.getRoot = function() {
|
||||
var root = this;
|
||||
while (root.getParent()) {
|
||||
root = root.getParent();
|
||||
}
|
||||
return root;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Builds a nested array structure from the node keys in this node's subtree to
|
||||
* facilitate testing tree operations that change the hierarchy.
|
||||
* @return {!Array<KEY>} The structure of this node's descendants as nested
|
||||
* array of node keys. The number of unclosed opening brackets up to a
|
||||
* particular node is proportional to the indentation of that node in the
|
||||
* graphical representation of the tree. Example:
|
||||
* <pre>
|
||||
* this
|
||||
* |- child1
|
||||
* | L- grandchild
|
||||
* L- child2
|
||||
* </pre>
|
||||
* is represented as ['child1', ['grandchild'], 'child2'].
|
||||
*/
|
||||
goog.structs.TreeNode.prototype.getSubtreeKeys = function() {
|
||||
var ret = [];
|
||||
this.forEachChild(function(child) {
|
||||
ret.push(child.getKey());
|
||||
if (!child.isLeaf()) {
|
||||
ret.push(child.getSubtreeKeys());
|
||||
}
|
||||
});
|
||||
return ret;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Tells whether this node is the ancestor of the given node.
|
||||
* @param {!goog.structs.TreeNode<KEY, VALUE>} node A node.
|
||||
* @return {boolean} Whether this node is the ancestor of {@code node}.
|
||||
*/
|
||||
goog.structs.TreeNode.prototype.contains = function(node) {
|
||||
var current = node;
|
||||
do {
|
||||
current = current.getParent();
|
||||
} while (current && current != this);
|
||||
return Boolean(current);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Finds the deepest common ancestor of the given nodes. The concept of
|
||||
* ancestor is not strict in this case, it includes the node itself.
|
||||
* @param {...!goog.structs.TreeNode<KEY, VALUE>} var_args The nodes.
|
||||
* @return {goog.structs.TreeNode<KEY, VALUE>} The common ancestor of the nodes
|
||||
* or null if they are from different trees.
|
||||
* @template KEY, VALUE
|
||||
*/
|
||||
goog.structs.TreeNode.findCommonAncestor = function(var_args) {
|
||||
/** @type {goog.structs.TreeNode} */
|
||||
var ret = arguments[0];
|
||||
if (!ret) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var retDepth = ret.getDepth();
|
||||
for (var i = 1; i < arguments.length; i++) {
|
||||
/** @type {goog.structs.TreeNode} */
|
||||
var node = arguments[i];
|
||||
var depth = node.getDepth();
|
||||
while (node != ret) {
|
||||
if (depth <= retDepth) {
|
||||
ret = ret.getParent();
|
||||
retDepth--;
|
||||
}
|
||||
if (depth > retDepth) {
|
||||
node = node.getParent();
|
||||
depth--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns a node whose key matches the given one in the hierarchy rooted at
|
||||
* this node. The hierarchy is searched using an in-order traversal.
|
||||
* @param {KEY} key The key to search for.
|
||||
* @return {goog.structs.TreeNode<KEY, VALUE>} The node with the given key, or
|
||||
* null if no node with the given key exists in the hierarchy.
|
||||
*/
|
||||
goog.structs.TreeNode.prototype.getNodeByKey = function(key) {
|
||||
if (this.getKey() == key) {
|
||||
return this;
|
||||
}
|
||||
var children = this.getChildren();
|
||||
for (var i = 0; i < children.length; i++) {
|
||||
var descendant = children[i].getNodeByKey(key);
|
||||
if (descendant) {
|
||||
return descendant;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Traverses all child nodes.
|
||||
* @param {function(this:THIS, !goog.structs.TreeNode<KEY, VALUE>, number,
|
||||
* !Array<!goog.structs.TreeNode<KEY, VALUE>>)} f Callback function. It
|
||||
* takes the node, its index and the array of all child nodes as arguments.
|
||||
* @param {THIS=} opt_this The object to be used as the value of {@code this}
|
||||
* within {@code f}.
|
||||
* @template THIS
|
||||
*/
|
||||
goog.structs.TreeNode.prototype.forEachChild = function(f, opt_this) {
|
||||
goog.array.forEach(this.getChildren(), f, opt_this);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Traverses all child nodes recursively in preorder.
|
||||
* @param {function(!goog.structs.TreeNode<KEY, VALUE>)} f Callback function.
|
||||
* It takes the node as argument.
|
||||
* @param {Object=} opt_this The object to be used as the value of {@code this}
|
||||
* within {@code f}.
|
||||
*/
|
||||
goog.structs.TreeNode.prototype.forEachDescendant = function(f, opt_this) {
|
||||
goog.array.forEach(this.getChildren(), function(child) {
|
||||
f.call(opt_this, child);
|
||||
child.forEachDescendant(f, opt_this);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Traverses the subtree with the possibility to skip branches. Starts with
|
||||
* this node, and visits the descendant nodes depth-first, in preorder.
|
||||
* @param {function(this:THIS, !goog.structs.TreeNode<KEY, VALUE>):
|
||||
* (boolean|undefined)} f Callback function. It takes the node as argument.
|
||||
* The children of this node will be visited if the callback returns true or
|
||||
* undefined, and will be skipped if the callback returns false.
|
||||
* @param {THIS=} opt_this The object to be used as the value of {@code this}
|
||||
* within {@code f}.
|
||||
* @template THIS
|
||||
*/
|
||||
goog.structs.TreeNode.prototype.traverse = function(f, opt_this) {
|
||||
if (f.call(opt_this, this) !== false) {
|
||||
goog.array.forEach(this.getChildren(), function(child) {
|
||||
child.traverse(f, opt_this);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sets the parent node of this node. The callers must ensure that the parent
|
||||
* node and only that has this node among its children.
|
||||
* @param {goog.structs.TreeNode<KEY, VALUE>} parent The parent to set. If
|
||||
* null, the node will be detached from the tree.
|
||||
* @protected
|
||||
*/
|
||||
goog.structs.TreeNode.prototype.setParent = function(parent) {
|
||||
this.parent_ = parent;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Appends a child node to this node.
|
||||
* @param {!goog.structs.TreeNode<KEY, VALUE>} child Orphan child node.
|
||||
*/
|
||||
goog.structs.TreeNode.prototype.addChild = function(child) {
|
||||
this.addChildAt(child, this.children_ ? this.children_.length : 0);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Inserts a child node at the given index.
|
||||
* @param {!goog.structs.TreeNode<KEY, VALUE>} child Orphan child node.
|
||||
* @param {number} index The position to insert at.
|
||||
*/
|
||||
goog.structs.TreeNode.prototype.addChildAt = function(child, index) {
|
||||
goog.asserts.assert(!child.getParent());
|
||||
child.setParent(this);
|
||||
this.children_ = this.children_ || [];
|
||||
goog.asserts.assert(index >= 0 && index <= this.children_.length);
|
||||
goog.array.insertAt(this.children_, child, index);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Replaces a child node at the given index.
|
||||
* @param {!goog.structs.TreeNode<KEY, VALUE>} newChild Child node to set. It
|
||||
* must not have parent node.
|
||||
* @param {number} index Valid index of the old child to replace.
|
||||
* @return {!goog.structs.TreeNode<KEY, VALUE>} The original child node,
|
||||
* detached from its parent.
|
||||
*/
|
||||
goog.structs.TreeNode.prototype.replaceChildAt = function(newChild, index) {
|
||||
goog.asserts.assert(!newChild.getParent(),
|
||||
'newChild must not have parent node');
|
||||
var children = this.getChildren();
|
||||
var oldChild = children[index];
|
||||
goog.asserts.assert(oldChild, 'Invalid child or child index is given.');
|
||||
oldChild.setParent(null);
|
||||
children[index] = newChild;
|
||||
newChild.setParent(this);
|
||||
return oldChild;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Replaces the given child node.
|
||||
* @param {!goog.structs.TreeNode<KEY, VALUE>} newChild New node to replace
|
||||
* {@code oldChild}. It must not have parent node.
|
||||
* @param {!goog.structs.TreeNode<KEY, VALUE>} oldChild Existing child node to
|
||||
* be replaced.
|
||||
* @return {!goog.structs.TreeNode<KEY, VALUE>} The replaced child node
|
||||
* detached from its parent.
|
||||
*/
|
||||
goog.structs.TreeNode.prototype.replaceChild = function(newChild, oldChild) {
|
||||
return this.replaceChildAt(newChild,
|
||||
goog.array.indexOf(this.getChildren(), oldChild));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Removes the child node at the given index.
|
||||
* @param {number} index The position to remove from.
|
||||
* @return {goog.structs.TreeNode<KEY, VALUE>} The removed node if any.
|
||||
*/
|
||||
goog.structs.TreeNode.prototype.removeChildAt = function(index) {
|
||||
var child = this.children_ && this.children_[index];
|
||||
if (child) {
|
||||
child.setParent(null);
|
||||
goog.array.removeAt(this.children_, index);
|
||||
if (this.children_.length == 0) {
|
||||
delete this.children_;
|
||||
}
|
||||
return child;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Removes the given child node of this node.
|
||||
* @param {goog.structs.TreeNode<KEY, VALUE>} child The node to remove.
|
||||
* @return {goog.structs.TreeNode<KEY, VALUE>} The removed node if any.
|
||||
*/
|
||||
goog.structs.TreeNode.prototype.removeChild = function(child) {
|
||||
return this.removeChildAt(goog.array.indexOf(this.getChildren(), child));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Removes all child nodes of this node.
|
||||
*/
|
||||
goog.structs.TreeNode.prototype.removeChildren = function() {
|
||||
if (this.children_) {
|
||||
goog.array.forEach(this.children_, function(child) {
|
||||
child.setParent(null);
|
||||
});
|
||||
}
|
||||
delete this.children_;
|
||||
};
|
||||
@@ -0,0 +1,24 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
Copyright 2010 The Closure Library Authors. All Rights Reserved.
|
||||
|
||||
Use of this source code is governed by the Apache License, Version 2.0.
|
||||
See the COPYING file for details.
|
||||
-->
|
||||
<!--
|
||||
-->
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<title>
|
||||
Closure Unit Tests - goog.structs.TreeNode
|
||||
</title>
|
||||
<script src="../base.js">
|
||||
</script>
|
||||
<script>
|
||||
goog.require('goog.structs.TreeNodeTest');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,384 @@
|
||||
// Copyright 2010 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.
|
||||
|
||||
goog.provide('goog.structs.TreeNodeTest');
|
||||
goog.setTestOnly('goog.structs.TreeNodeTest');
|
||||
|
||||
goog.require('goog.structs.TreeNode');
|
||||
goog.require('goog.testing.jsunit');
|
||||
|
||||
function testConstructor() {
|
||||
var node = new goog.structs.TreeNode('key', 'value');
|
||||
assertEquals('key', 'key', node.getKey());
|
||||
assertEquals('value', 'value', node.getValue());
|
||||
assertNull('parent', node.getParent());
|
||||
assertArrayEquals('children', [], node.getChildren());
|
||||
assertTrue('leaf', node.isLeaf());
|
||||
}
|
||||
|
||||
function testClone() {
|
||||
var node1 = new goog.structs.TreeNode(1, '1');
|
||||
var node2 = new goog.structs.TreeNode(2, '2');
|
||||
var node3 = new goog.structs.TreeNode(3, '3');
|
||||
node1.addChild(node2);
|
||||
node2.addChild(node3);
|
||||
|
||||
var clone = node2.clone();
|
||||
assertEquals('key', 2, clone.getKey());
|
||||
assertEquals('value', '2', clone.getValue());
|
||||
assertNull('parent', clone.getParent());
|
||||
assertArrayEquals('children', [], clone.getChildren());
|
||||
}
|
||||
|
||||
function testDeepClone() {
|
||||
var node1 = new goog.structs.TreeNode(1, '1');
|
||||
var node2 = new goog.structs.TreeNode(2, '2');
|
||||
var node3 = new goog.structs.TreeNode(3, '3');
|
||||
node1.addChild(node2);
|
||||
node2.addChild(node3);
|
||||
|
||||
var clone = node2.deepClone();
|
||||
assertEquals('key', 2, clone.getKey());
|
||||
assertEquals('value', '2', clone.getValue());
|
||||
assertNull('parent', clone.getParent());
|
||||
assertEquals('number of children', 1, clone.getChildren().length);
|
||||
assertEquals('first child key', 3, clone.getChildAt(0).getKey());
|
||||
assertNotEquals('first child has been cloned', node3, clone.getChildAt(0));
|
||||
}
|
||||
|
||||
function testGetParent() {
|
||||
var node1 = new goog.structs.TreeNode(1, null);
|
||||
var node2 = new goog.structs.TreeNode(2, null);
|
||||
node1.addChild(node2);
|
||||
assertEquals('parent', node1, node2.getParent());
|
||||
assertNull('orphan', node1.getParent());
|
||||
}
|
||||
|
||||
function testIsLeaf() {
|
||||
var node1 = new goog.structs.TreeNode(1, null);
|
||||
var node2 = new goog.structs.TreeNode(2, null);
|
||||
node1.addChild(node2);
|
||||
assertFalse('not leaf', node1.isLeaf());
|
||||
assertTrue('leaf', node2.isLeaf());
|
||||
}
|
||||
|
||||
function testIsLastChild() {
|
||||
var node1 = new goog.structs.TreeNode(1, '1');
|
||||
var node2 = new goog.structs.TreeNode(2, '2');
|
||||
var node3 = new goog.structs.TreeNode(3, '3');
|
||||
node1.addChild(node2);
|
||||
node1.addChild(node3);
|
||||
assertFalse('root', node1.isLastChild());
|
||||
assertFalse('first child out of the two', node2.isLastChild());
|
||||
assertTrue('second child out of the two', node3.isLastChild());
|
||||
}
|
||||
|
||||
function testGetChildren() {
|
||||
var node1 = new goog.structs.TreeNode(1, null);
|
||||
var node2 = new goog.structs.TreeNode(2, null);
|
||||
node1.addChild(node2);
|
||||
assertArrayEquals('1 child', [node2], node1.getChildren());
|
||||
assertArrayEquals('no children', [], node2.getChildren());
|
||||
}
|
||||
|
||||
function testGetChildAt() {
|
||||
var node1 = new goog.structs.TreeNode(1, null);
|
||||
var node2 = new goog.structs.TreeNode(2, null);
|
||||
node1.addChild(node2);
|
||||
assertNull('index too low', node1.getChildAt(-1));
|
||||
assertEquals('first child', node2, node1.getChildAt(0));
|
||||
assertNull('index too high', node1.getChildAt(1));
|
||||
assertNull('no children', node2.getChildAt(0));
|
||||
}
|
||||
|
||||
function testGetChildCount() {
|
||||
var node1 = new goog.structs.TreeNode(1, null);
|
||||
var node2 = new goog.structs.TreeNode(2, null);
|
||||
node1.addChild(node2);
|
||||
assertEquals('child count of root node', 1, node1.getChildCount());
|
||||
assertEquals('child count of leaf node', 0, node2.getChildCount());
|
||||
}
|
||||
|
||||
function testGetDepth() {
|
||||
var node1 = new goog.structs.TreeNode(1, null);
|
||||
var node2 = new goog.structs.TreeNode(2, null);
|
||||
var node3 = new goog.structs.TreeNode(3, null);
|
||||
node1.addChild(node2);
|
||||
node2.addChild(node3);
|
||||
assertEquals('no parent', 0, node1.getDepth());
|
||||
assertEquals('1 ancestor', 1, node2.getDepth());
|
||||
assertEquals('2 ancestors', 2, node3.getDepth());
|
||||
}
|
||||
|
||||
function testGetAncestors() {
|
||||
var node1 = new goog.structs.TreeNode(1, null);
|
||||
var node2 = new goog.structs.TreeNode(2, null);
|
||||
var node3 = new goog.structs.TreeNode(3, null);
|
||||
node1.addChild(node2);
|
||||
node2.addChild(node3);
|
||||
assertArrayEquals('no ancestors', [], node1.getAncestors());
|
||||
assertArrayEquals('2 ancestors', [node2, node1], node3.getAncestors());
|
||||
}
|
||||
|
||||
function testGetRoot() {
|
||||
var node1 = new goog.structs.TreeNode(1, null);
|
||||
var node2 = new goog.structs.TreeNode(2, null);
|
||||
node1.addChild(node2);
|
||||
assertEquals('no ancestors', node1, node1.getRoot());
|
||||
assertEquals('has ancestors', node1, node2.getRoot());
|
||||
}
|
||||
|
||||
function testGetSubtreeKeys() {
|
||||
var root = new goog.structs.TreeNode('root', null);
|
||||
var child1 = new goog.structs.TreeNode('child1', null);
|
||||
var child2 = new goog.structs.TreeNode('child2', null);
|
||||
var grandchild = new goog.structs.TreeNode('grandchild', null);
|
||||
root.addChild(child1);
|
||||
root.addChild(child2);
|
||||
child1.addChild(grandchild);
|
||||
assertArrayEquals('node hierarchy', ['child1', ['grandchild'], 'child2'],
|
||||
root.getSubtreeKeys());
|
||||
}
|
||||
|
||||
function testContains() {
|
||||
var node1 = new goog.structs.TreeNode(1, null);
|
||||
var node2 = new goog.structs.TreeNode(2, null);
|
||||
var node3 = new goog.structs.TreeNode(3, null);
|
||||
var node4 = new goog.structs.TreeNode(4, null);
|
||||
node1.addChild(node2);
|
||||
node2.addChild(node3);
|
||||
node2.addChild(node4);
|
||||
|
||||
assertTrue('parent', node1.contains(node2));
|
||||
assertTrue('grandparent', node1.contains(node3));
|
||||
assertFalse('child', node2.contains(node1));
|
||||
assertFalse('grandchild', node3.contains(node1));
|
||||
assertFalse('sibling', node3.contains(node4));
|
||||
}
|
||||
|
||||
function testFindCommonAncestor() {
|
||||
var findCommonAncestor = goog.structs.TreeNode.findCommonAncestor;
|
||||
var node1 = new goog.structs.TreeNode(1, null);
|
||||
var node2 = new goog.structs.TreeNode(2, null);
|
||||
var node3 = new goog.structs.TreeNode(3, null);
|
||||
var node4 = new goog.structs.TreeNode(4, null);
|
||||
var node5 = new goog.structs.TreeNode(5, null);
|
||||
node1.addChild(node2);
|
||||
node2.addChild(node3);
|
||||
node1.addChild(node4);
|
||||
|
||||
assertNull('0 nodes', findCommonAncestor());
|
||||
assertEquals('1 node', node2, findCommonAncestor(node2));
|
||||
assertEquals('same nodes', node3, findCommonAncestor(node3, node3));
|
||||
assertEquals('node and child node', node2, findCommonAncestor(node2, node3));
|
||||
assertEquals('node and parent node', node1, findCommonAncestor(node2, node1));
|
||||
assertEquals('siblings', node1, findCommonAncestor(node2, node4));
|
||||
assertEquals('all nodes', node1,
|
||||
findCommonAncestor(node2, node3, node4, node1));
|
||||
assertNull('disconnected nodes', findCommonAncestor(node3, node5));
|
||||
}
|
||||
|
||||
function testGetNodeByKey() {
|
||||
var node1 = new goog.structs.TreeNode(1, null);
|
||||
var node2 = new goog.structs.TreeNode(2, null);
|
||||
var node3 = new goog.structs.TreeNode(3, null);
|
||||
var node4 = new goog.structs.TreeNode(4, null);
|
||||
var node5 = new goog.structs.TreeNode(2, null);
|
||||
var node6 = new goog.structs.TreeNode(3, null);
|
||||
node1.addChild(node2);
|
||||
node2.addChild(node3);
|
||||
node1.addChild(node4);
|
||||
node4.addChild(node5);
|
||||
node1.addChild(node6);
|
||||
|
||||
assertEquals('root', node1, node1.getNodeByKey(1));
|
||||
assertEquals('child with unique key', node5, node4.getNodeByKey(2));
|
||||
assertEquals('child with duplicate keys', node2, node1.getNodeByKey(2));
|
||||
assertEquals('grandchild with duplicate keys', node3, node1.getNodeByKey(3));
|
||||
assertNull('disconnected', node2.getNodeByKey(4));
|
||||
assertNull('missing', node1.getNodeByKey(5));
|
||||
}
|
||||
|
||||
function testForEachChild() {
|
||||
var node1 = new goog.structs.TreeNode(1, null);
|
||||
var node2 = new goog.structs.TreeNode(2, null);
|
||||
var node3 = new goog.structs.TreeNode(3, null);
|
||||
node1.addChild(node2);
|
||||
node1.addChild(node3);
|
||||
|
||||
var thisContext = {};
|
||||
var visitedNodes = [];
|
||||
var indices = [];
|
||||
node1.forEachChild(function(node, index, children) {
|
||||
assertEquals('value of this', thisContext, this);
|
||||
visitedNodes.push(node);
|
||||
indices.push(index);
|
||||
assertArrayEquals('children argument', [node2, node3], children);
|
||||
}, thisContext);
|
||||
assertArrayEquals('visited nodes', [node2, node3], visitedNodes);
|
||||
assertArrayEquals('index of visited nodes', [0, 1], indices);
|
||||
}
|
||||
|
||||
function testForEachDescendant() {
|
||||
var node1 = new goog.structs.TreeNode(1, null);
|
||||
var node2 = new goog.structs.TreeNode(2, null);
|
||||
var node3 = new goog.structs.TreeNode(3, null);
|
||||
var node4 = new goog.structs.TreeNode(4, null);
|
||||
node1.addChild(node2);
|
||||
node2.addChild(node3);
|
||||
node2.addChild(node4);
|
||||
|
||||
var thisContext = {};
|
||||
var visitedNodes = [];
|
||||
node1.forEachDescendant(function(node) {
|
||||
assertEquals('value of this', thisContext, this);
|
||||
visitedNodes.push(node);
|
||||
}, thisContext);
|
||||
assertArrayEquals('visited nodes', [node2, node3, node4], visitedNodes);
|
||||
}
|
||||
|
||||
function testTraverse() {
|
||||
var node1 = new goog.structs.TreeNode(1, null);
|
||||
var node2 = new goog.structs.TreeNode(2, null);
|
||||
var node3 = new goog.structs.TreeNode(3, null);
|
||||
var node4 = new goog.structs.TreeNode(4, null);
|
||||
node1.addChild(node2);
|
||||
node2.addChild(node3);
|
||||
node2.addChild(node4);
|
||||
|
||||
var thisContext = {};
|
||||
var visitedNodes = [];
|
||||
node1.traverse(function(node) {
|
||||
assertEquals('value of this', thisContext, this);
|
||||
visitedNodes.push(node);
|
||||
}, thisContext);
|
||||
assertArrayEquals('callback returns nothing => all nodes are visited',
|
||||
[node1, node2, node3, node4], visitedNodes);
|
||||
|
||||
visitedNodes = [];
|
||||
node1.traverse(function(node) {
|
||||
visitedNodes.push(node);
|
||||
return node != node2; // Cut off at node2.
|
||||
});
|
||||
assertArrayEquals('children of node2 are skipped',
|
||||
[node1, node2], visitedNodes);
|
||||
}
|
||||
|
||||
function testAddChild() {
|
||||
var node1 = new goog.structs.TreeNode(1, null);
|
||||
var node2 = new goog.structs.TreeNode(2, null);
|
||||
var node3 = new goog.structs.TreeNode(3, null);
|
||||
assertArrayEquals('0 children', [], node1.getChildren());
|
||||
node1.addChild(node2);
|
||||
assertArrayEquals('1 child', [node2], node1.getChildren());
|
||||
assertEquals('parent is set', node1, node2.getParent());
|
||||
node1.addChild(node3);
|
||||
assertArrayEquals('2 children', [node2, node3], node1.getChildren());
|
||||
}
|
||||
|
||||
function testAddChildAt() {
|
||||
var node1 = new goog.structs.TreeNode(1, null);
|
||||
var node2 = new goog.structs.TreeNode(2, null);
|
||||
var node3 = new goog.structs.TreeNode(3, null);
|
||||
var node4 = new goog.structs.TreeNode(4, null);
|
||||
var node5 = new goog.structs.TreeNode(5, null);
|
||||
node1.addChildAt(node2, 0);
|
||||
assertArrayEquals('add first child', [node2], node1.getChildren());
|
||||
assertEquals('parent is set', node1, node2.getParent());
|
||||
node1.addChildAt(node3, 0);
|
||||
assertArrayEquals('add to the front', [node3, node2],
|
||||
node1.getChildren());
|
||||
node1.addChildAt(node4, 1);
|
||||
assertArrayEquals('add to the middle', [node3, node4, node2],
|
||||
node1.getChildren());
|
||||
node1.addChildAt(node5, 3);
|
||||
assertArrayEquals('add to the end', [node3, node4, node2, node5],
|
||||
node1.getChildren());
|
||||
}
|
||||
|
||||
function testReplaceChildAt() {
|
||||
var root = new goog.structs.TreeNode(0, null);
|
||||
var node1 = new goog.structs.TreeNode(1, null);
|
||||
root.addChild(node1);
|
||||
|
||||
var node2 = new goog.structs.TreeNode(2, null);
|
||||
assertEquals('original node', node1, root.replaceChildAt(node2, 0));
|
||||
assertEquals('parent is set', root, node2.getParent());
|
||||
assertArrayEquals('children are updated', [node2], root.getChildren());
|
||||
assertNull('old child node is detached', node1.getParent());
|
||||
}
|
||||
|
||||
function testReplaceChild() {
|
||||
var root = new goog.structs.TreeNode(0, null);
|
||||
var node1 = new goog.structs.TreeNode(1, null);
|
||||
root.addChild(node1);
|
||||
|
||||
var node2 = new goog.structs.TreeNode(2, null);
|
||||
assertEquals('original node', node1, root.replaceChild(node2, node1));
|
||||
assertEquals('parent is set', root, node2.getParent());
|
||||
assertArrayEquals('children are updated', [node2], root.getChildren());
|
||||
assertNull('old child node is detached', node1.getParent());
|
||||
}
|
||||
|
||||
function testRemoveChildAt() {
|
||||
var node1 = new goog.structs.TreeNode(1, null);
|
||||
var node2 = new goog.structs.TreeNode(2, null);
|
||||
var node3 = new goog.structs.TreeNode(3, null);
|
||||
node1.addChild(node2);
|
||||
node1.addChild(node3);
|
||||
|
||||
assertNull('index too low', node1.removeChildAt(-1));
|
||||
assertNull('index too high', node1.removeChildAt(2));
|
||||
assertArrayEquals('node1 is intact', [node2, node3], node1.getChildren());
|
||||
|
||||
assertEquals('remove first child', node2, node1.removeChildAt(0));
|
||||
assertArrayEquals('remove from the front', [node3], node1.getChildren());
|
||||
assertNull('parent is unset', node2.getParent());
|
||||
|
||||
assertEquals('remove last child', node3, node1.removeChildAt(0));
|
||||
assertArrayEquals('remove last child', [], node1.getChildren());
|
||||
assertTrue('node1 became leaf', node1.isLeaf());
|
||||
}
|
||||
|
||||
function testRemoveChild() {
|
||||
var node1 = new goog.structs.TreeNode(1, null);
|
||||
var node2 = new goog.structs.TreeNode(2, null);
|
||||
var node3 = new goog.structs.TreeNode(3, null);
|
||||
node1.addChild(node2);
|
||||
node1.addChild(node3);
|
||||
|
||||
assertNull('remove null', node1.removeChild(null));
|
||||
assertNull('remove non-child', node1.removeChild(node1));
|
||||
assertArrayEquals('node1 is intact', [node2, node3], node1.getChildren());
|
||||
|
||||
assertEquals('remove node3, return value', node3, node1.removeChild(node3));
|
||||
assertArrayEquals('node is removed', [node2], node1.getChildren());
|
||||
}
|
||||
|
||||
function testRemoveChildren() {
|
||||
var node1 = new goog.structs.TreeNode(1, null);
|
||||
var node2 = new goog.structs.TreeNode(2, null);
|
||||
var node3 = new goog.structs.TreeNode(3, null);
|
||||
node1.addChild(node2);
|
||||
node1.addChild(node3);
|
||||
|
||||
node2.removeChildren();
|
||||
assertArrayEquals('removing a leaf node\'s children has no effect', [],
|
||||
node2.getChildren());
|
||||
assertEquals('node still has parent', node1, node2.getParent());
|
||||
|
||||
node1.removeChildren();
|
||||
assertArrayEquals('children have been removed', [], node1.getChildren());
|
||||
assertNull('children\'s parents have been unset', node2.getParent());
|
||||
}
|
||||
@@ -0,0 +1,395 @@
|
||||
// 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: Trie.
|
||||
*
|
||||
*
|
||||
* This file provides the implementation of a trie data structure. A trie is a
|
||||
* data structure that stores key/value pairs in a prefix tree. See:
|
||||
* http://en.wikipedia.org/wiki/Trie
|
||||
*/
|
||||
|
||||
|
||||
goog.provide('goog.structs.Trie');
|
||||
|
||||
goog.require('goog.object');
|
||||
goog.require('goog.structs');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Class for a Trie datastructure. Trie data structures are made out of trees
|
||||
* of Trie classes.
|
||||
*
|
||||
* @param {goog.structs.Trie<VALUE>|Object<string, VALUE>=} opt_trie Optional
|
||||
* goog.structs.Trie or Object to initialize trie with.
|
||||
* @constructor
|
||||
* @template VALUE
|
||||
*/
|
||||
goog.structs.Trie = function(opt_trie) {
|
||||
/**
|
||||
* This trie's value. For the base trie, this will be the value of the
|
||||
* empty key, if defined.
|
||||
* @private {VALUE}
|
||||
*/
|
||||
this.value_ = undefined;
|
||||
|
||||
/**
|
||||
* This trie's child nodes.
|
||||
* @private {!Object<!goog.structs.Trie<VALUE>>}
|
||||
*/
|
||||
this.childNodes_ = {};
|
||||
|
||||
if (opt_trie) {
|
||||
this.setAll(opt_trie);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sets the given key/value pair in the trie. O(L), where L is the length
|
||||
* of the key.
|
||||
* @param {string} key The key.
|
||||
* @param {VALUE} value The value.
|
||||
*/
|
||||
goog.structs.Trie.prototype.set = function(key, value) {
|
||||
this.setOrAdd_(key, value, false);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Adds the given key/value pair in the trie. Throw an exception if the key
|
||||
* already exists in the trie. O(L), where L is the length of the key.
|
||||
* @param {string} key The key.
|
||||
* @param {VALUE} value The value.
|
||||
*/
|
||||
goog.structs.Trie.prototype.add = function(key, value) {
|
||||
this.setOrAdd_(key, value, true);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Helper function for set and add. Adds the given key/value pair to
|
||||
* the trie, or, if the key already exists, sets the value of the key. If
|
||||
* opt_add is true, then throws an exception if the key already has a value in
|
||||
* the trie. O(L), where L is the length of the key.
|
||||
* @param {string} key The key.
|
||||
* @param {VALUE} value The value.
|
||||
* @param {boolean=} opt_add Throw exception if key is already in the trie.
|
||||
* @private
|
||||
*/
|
||||
goog.structs.Trie.prototype.setOrAdd_ = function(key, value, opt_add) {
|
||||
var node = this;
|
||||
for (var characterPosition = 0; characterPosition < key.length;
|
||||
characterPosition++) {
|
||||
var currentCharacter = key.charAt(characterPosition);
|
||||
if (!node.childNodes_[currentCharacter]) {
|
||||
node.childNodes_[currentCharacter] = new goog.structs.Trie();
|
||||
}
|
||||
node = node.childNodes_[currentCharacter];
|
||||
}
|
||||
if (opt_add && node.value_ !== undefined) {
|
||||
throw Error('The collection already contains the key "' + key + '"');
|
||||
} else {
|
||||
node.value_ = value;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Adds multiple key/value pairs from another goog.structs.Trie or Object.
|
||||
* O(N) where N is the number of nodes in the trie.
|
||||
* @param {!Object<string, VALUE>|!goog.structs.Trie<VALUE>} trie Object
|
||||
* containing the data to add.
|
||||
*/
|
||||
goog.structs.Trie.prototype.setAll = function(trie) {
|
||||
var keys = goog.structs.getKeys(trie);
|
||||
var values = goog.structs.getValues(trie);
|
||||
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
this.set(keys[i], values[i]);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Traverse along the given path, returns the child node at ending.
|
||||
* Returns undefined if node for the path doesn't exist.
|
||||
* @param {string} path The path to traverse.
|
||||
* @return {!goog.structs.Trie<VALUE>|undefined}
|
||||
* @private
|
||||
*/
|
||||
goog.structs.Trie.prototype.getChildNode_ = function(path) {
|
||||
var node = this;
|
||||
for (var characterPosition = 0; characterPosition < path.length;
|
||||
characterPosition++) {
|
||||
var currentCharacter = path.charAt(characterPosition);
|
||||
node = node.childNodes_[currentCharacter];
|
||||
if (!node) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
return node;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Retrieves a value from the trie given a key. O(L), where L is the length of
|
||||
* the key.
|
||||
* @param {string} key The key to retrieve from the trie.
|
||||
* @return {VALUE|undefined} The value of the key in the trie, or undefined if
|
||||
* the trie does not contain this key.
|
||||
*/
|
||||
goog.structs.Trie.prototype.get = function(key) {
|
||||
var node = this.getChildNode_(key);
|
||||
return node ? node.value_ : undefined;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Retrieves all values from the trie that correspond to prefixes of the given
|
||||
* input key. O(L), where L is the length of the key.
|
||||
*
|
||||
* @param {string} key The key to use for lookup. The given key as well as all
|
||||
* prefixes of the key are retrieved.
|
||||
* @param {?number=} opt_keyStartIndex Optional position in key to start lookup
|
||||
* from. Defaults to 0 if not specified.
|
||||
* @return {!Object<string, VALUE>} Map of end index of matching prefixes and
|
||||
* corresponding values. Empty if no match found.
|
||||
*/
|
||||
goog.structs.Trie.prototype.getKeyAndPrefixes = function(key,
|
||||
opt_keyStartIndex) {
|
||||
var node = this;
|
||||
var matches = {};
|
||||
var characterPosition = opt_keyStartIndex || 0;
|
||||
|
||||
if (node.value_ !== undefined) {
|
||||
matches[characterPosition] = node.value_;
|
||||
}
|
||||
|
||||
for (; characterPosition < key.length; characterPosition++) {
|
||||
var currentCharacter = key.charAt(characterPosition);
|
||||
if (!(currentCharacter in node.childNodes_)) {
|
||||
break;
|
||||
}
|
||||
node = node.childNodes_[currentCharacter];
|
||||
if (node.value_ !== undefined) {
|
||||
matches[characterPosition] = node.value_;
|
||||
}
|
||||
}
|
||||
|
||||
return matches;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the values of the trie. Not returned in any reliable order. O(N) where
|
||||
* N is the number of nodes in the trie. Calls getValuesInternal_.
|
||||
* @return {!Array<VALUE>} The values in the trie.
|
||||
*/
|
||||
goog.structs.Trie.prototype.getValues = function() {
|
||||
var allValues = [];
|
||||
this.getValuesInternal_(allValues);
|
||||
return allValues;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the values of the trie. Not returned in any reliable order. O(N) where
|
||||
* N is the number of nodes in the trie. Builds the values as it goes.
|
||||
* @param {!Array<VALUE>} allValues Array to place values into.
|
||||
* @private
|
||||
*/
|
||||
goog.structs.Trie.prototype.getValuesInternal_ = function(allValues) {
|
||||
if (this.value_ !== undefined) {
|
||||
allValues.push(this.value_);
|
||||
}
|
||||
for (var childNode in this.childNodes_) {
|
||||
this.childNodes_[childNode].getValuesInternal_(allValues);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the keys of the trie. Not returned in any reliable order. O(N) where
|
||||
* N is the number of nodes in the trie (or prefix subtree).
|
||||
* @param {string=} opt_prefix Find only keys with this optional prefix.
|
||||
* @return {!Array<string>} The keys in the trie.
|
||||
*/
|
||||
goog.structs.Trie.prototype.getKeys = function(opt_prefix) {
|
||||
var allKeys = [];
|
||||
if (opt_prefix) {
|
||||
// Traverse to the given prefix, then call getKeysInternal_ to dump the
|
||||
// keys below that point.
|
||||
var node = this;
|
||||
for (var characterPosition = 0; characterPosition < opt_prefix.length;
|
||||
characterPosition++) {
|
||||
var currentCharacter = opt_prefix.charAt(characterPosition);
|
||||
if (!node.childNodes_[currentCharacter]) {
|
||||
return [];
|
||||
}
|
||||
node = node.childNodes_[currentCharacter];
|
||||
}
|
||||
node.getKeysInternal_(opt_prefix, allKeys);
|
||||
} else {
|
||||
this.getKeysInternal_('', allKeys);
|
||||
}
|
||||
return allKeys;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Private method to get keys from the trie. Builds the keys as it goes.
|
||||
* @param {string} keySoFar The partial key (prefix) traversed so far.
|
||||
* @param {!Array<string>} allKeys The partially built array of keys seen so
|
||||
* far.
|
||||
* @private
|
||||
*/
|
||||
goog.structs.Trie.prototype.getKeysInternal_ = function(keySoFar, allKeys) {
|
||||
if (this.value_ !== undefined) {
|
||||
allKeys.push(keySoFar);
|
||||
}
|
||||
for (var childNode in this.childNodes_) {
|
||||
this.childNodes_[childNode].getKeysInternal_(keySoFar + childNode, allKeys);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Checks to see if a certain key is in the trie. O(L), where L is the length
|
||||
* of the key.
|
||||
* @param {string} key A key that may be in the trie.
|
||||
* @return {boolean} Whether the trie contains key.
|
||||
*/
|
||||
goog.structs.Trie.prototype.containsKey = function(key) {
|
||||
return this.get(key) !== undefined;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Checks to see if a certain prefix is in the trie. O(L), where L is the length
|
||||
* of the prefix.
|
||||
* @param {string} prefix A prefix that may be in the trie.
|
||||
* @return {boolean} Whether any key of the trie has the prefix.
|
||||
*/
|
||||
goog.structs.Trie.prototype.containsPrefix = function(prefix) {
|
||||
// Empty string is any key's prefix.
|
||||
if (prefix.length == 0) {
|
||||
return !this.isEmpty();
|
||||
}
|
||||
return !!this.getChildNode_(prefix);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Checks to see if a certain value is in the trie. Worst case is O(N) where
|
||||
* N is the number of nodes in the trie.
|
||||
* @param {VALUE} value A value that may be in the trie.
|
||||
* @return {boolean} Whether the trie contains the value.
|
||||
*/
|
||||
goog.structs.Trie.prototype.containsValue = function(value) {
|
||||
if (this.value_ === value) {
|
||||
return true;
|
||||
}
|
||||
for (var childNode in this.childNodes_) {
|
||||
if (this.childNodes_[childNode].containsValue(value)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Completely empties a trie of all keys and values. ~O(1)
|
||||
*/
|
||||
goog.structs.Trie.prototype.clear = function() {
|
||||
this.childNodes_ = {};
|
||||
this.value_ = undefined;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Removes a key from the trie or throws an exception if the key is not in the
|
||||
* trie. O(L), where L is the length of the key.
|
||||
* @param {string} key A key that should be removed from the trie.
|
||||
* @return {VALUE} The value whose key was removed.
|
||||
*/
|
||||
goog.structs.Trie.prototype.remove = function(key) {
|
||||
var node = this;
|
||||
var parents = [];
|
||||
for (var characterPosition = 0; characterPosition < key.length;
|
||||
characterPosition++) {
|
||||
var currentCharacter = key.charAt(characterPosition);
|
||||
if (!node.childNodes_[currentCharacter]) {
|
||||
throw Error('The collection does not have the key "' + key + '"');
|
||||
}
|
||||
|
||||
// Archive the current parent and child name (key in childNodes_) so that
|
||||
// we may remove the following node and its parents if they are empty.
|
||||
parents.push([node, currentCharacter]);
|
||||
|
||||
node = node.childNodes_[currentCharacter];
|
||||
}
|
||||
var oldValue = node.value_;
|
||||
delete node.value_;
|
||||
|
||||
while (parents.length > 0) {
|
||||
var currentParentAndCharacter = parents.pop();
|
||||
var currentParent = currentParentAndCharacter[0];
|
||||
var currentCharacter = currentParentAndCharacter[1];
|
||||
if (currentParent.childNodes_[currentCharacter].isEmpty()) {
|
||||
// If the child is empty, then remove it.
|
||||
delete currentParent.childNodes_[currentCharacter];
|
||||
} else {
|
||||
// No point of traversing back any further, since we can't remove this
|
||||
// path.
|
||||
break;
|
||||
}
|
||||
}
|
||||
return oldValue;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Clones a trie and returns a new trie. O(N), where N is the number of nodes
|
||||
* in the trie.
|
||||
* @return {!goog.structs.Trie<VALUE>} A new goog.structs.Trie with the same
|
||||
* key value pairs.
|
||||
*/
|
||||
goog.structs.Trie.prototype.clone = function() {
|
||||
return new goog.structs.Trie(this);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the number of key value pairs in the trie. O(N), where N is the
|
||||
* number of nodes in the trie.
|
||||
* TODO: This could be optimized by storing a weight (count below) in every
|
||||
* node.
|
||||
* @return {number} The number of pairs.
|
||||
*/
|
||||
goog.structs.Trie.prototype.getCount = function() {
|
||||
return goog.structs.getCount(this.getValues());
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns true if this trie contains no elements. ~O(1).
|
||||
* @return {boolean} True iff this trie contains no elements.
|
||||
*/
|
||||
goog.structs.Trie.prototype.isEmpty = function() {
|
||||
return this.value_ === undefined && goog.object.isEmpty(this.childNodes_);
|
||||
};
|
||||
@@ -0,0 +1,24 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
Copyright 2007 The Closure Library Authors. All Rights Reserved.
|
||||
|
||||
Use of this source code is governed by the Apache License, Version 2.0.
|
||||
See the COPYING file for details.
|
||||
-->
|
||||
<!--
|
||||
-->
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<title>
|
||||
Closure Unit Tests - goog.structs.Trie
|
||||
</title>
|
||||
<script src="../base.js">
|
||||
</script>
|
||||
<script>
|
||||
goog.require('goog.structs.TrieTest');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,454 @@
|
||||
// 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.
|
||||
|
||||
goog.provide('goog.structs.TrieTest');
|
||||
goog.setTestOnly('goog.structs.TrieTest');
|
||||
|
||||
goog.require('goog.object');
|
||||
goog.require('goog.structs');
|
||||
goog.require('goog.structs.Trie');
|
||||
goog.require('goog.testing.jsunit');
|
||||
|
||||
function makeTrie() {
|
||||
var trie = new goog.structs.Trie();
|
||||
trie.add('hello', 1);
|
||||
trie.add('hi', 'howdy');
|
||||
trie.add('', 'an empty string key');
|
||||
trie.add('empty value', '');
|
||||
trie.add('zero', 0);
|
||||
trie.add('object', {});
|
||||
trie.add('null', null);
|
||||
trie.add('hello, world', 2);
|
||||
trie.add('world', {});
|
||||
return trie;
|
||||
}
|
||||
|
||||
function checkTrie(trie) {
|
||||
assertEquals('get, should be 1', trie.get('hello'), 1);
|
||||
assertEquals('get, should be "howdy"', trie.get('hi'), 'howdy');
|
||||
assertEquals('get, should be "an empty string key"', trie.get(''),
|
||||
'an empty string key');
|
||||
assertEquals('get, should be ""', trie.get('empty value'), '');
|
||||
assertEquals('get, should be ""', typeof trie.get('empty value'), 'string');
|
||||
assertEquals('get, should be an object', typeof trie.get('object'), 'object');
|
||||
assertEquals('get, should be 0', trie.get('zero'), 0);
|
||||
assertEquals('get "null", should be null', trie.get('null'), null);
|
||||
assertEquals('get, should be 2', trie.get('hello, world'), 2);
|
||||
assertEquals('get, should be an object', typeof trie.get('world'), 'object');
|
||||
}
|
||||
|
||||
function testTrieFormation() {
|
||||
var t = makeTrie();
|
||||
checkTrie(t);
|
||||
}
|
||||
|
||||
function testFailureOfMultipleAdds() {
|
||||
var t = new goog.structs.Trie();
|
||||
t.add('hello', 'testing');
|
||||
assertThrows('Error should be thrown when same key added twice.', function() {
|
||||
t.add('hello', 'test');
|
||||
});
|
||||
|
||||
t = new goog.structs.Trie();
|
||||
t.add('null', null);
|
||||
assertThrows('Error should be thrown when same key added twice.', function() {
|
||||
t.add('null', 'hi!');
|
||||
});
|
||||
|
||||
t = new goog.structs.Trie();
|
||||
t.add('null', 'blah');
|
||||
assertThrows('Error should be thrown when same key added twice.', function() {
|
||||
t.add('null', null);
|
||||
});
|
||||
}
|
||||
|
||||
function testTrieClone() {
|
||||
var trieOne = makeTrie();
|
||||
var trieTwo = new goog.structs.Trie(trieOne);
|
||||
checkTrie(trieTwo);
|
||||
}
|
||||
|
||||
function testTrieFromObject() {
|
||||
var someObject = {'hello' : 1,
|
||||
'hi' : 'howdy',
|
||||
'' : 'an empty string key',
|
||||
'empty value' : '',
|
||||
'object' : {},
|
||||
'zero' : 0,
|
||||
'null' : null,
|
||||
'hello, world' : 2,
|
||||
'world' : {}};
|
||||
var trie = new goog.structs.Trie(someObject);
|
||||
checkTrie(trie);
|
||||
}
|
||||
|
||||
function testTrieGetValues() {
|
||||
var trie = makeTrie();
|
||||
var values = trie.getValues();
|
||||
assertTrue('getValues, should contain "howdy"',
|
||||
goog.object.contains(values, 'howdy'));
|
||||
assertTrue('getValues, should contain 1', goog.object.contains(values, 1));
|
||||
assertTrue('getValues, should contain 0', goog.object.contains(values, 0));
|
||||
assertTrue('getValues, should contain ""', goog.object.contains(values, ''));
|
||||
assertTrue('getValues, should contain null',
|
||||
goog.object.contains(values, null));
|
||||
assertEquals('goog.structs.getCount(getValues()) should be 9',
|
||||
goog.structs.getCount(values), 9);
|
||||
}
|
||||
|
||||
function testTrieGetKeys() {
|
||||
var trie = makeTrie();
|
||||
var keys = trie.getKeys();
|
||||
assertTrue('getKeys, should contain "hello"',
|
||||
goog.object.contains(keys, 'hello'));
|
||||
assertTrue('getKeys, should contain "empty value"',
|
||||
goog.object.contains(keys, 'empty value'));
|
||||
assertTrue('getKeys, should contain ""', goog.object.contains(keys, ''));
|
||||
assertTrue('getKeys, should contain "zero"',
|
||||
goog.object.contains(keys, 'zero'));
|
||||
assertEquals('goog.structs.getCount(getKeys()) should be 9',
|
||||
goog.structs.getCount(keys), 9);
|
||||
}
|
||||
|
||||
|
||||
function testTrieCount() {
|
||||
var trieOne = makeTrie();
|
||||
var trieTwo = new goog.structs.Trie();
|
||||
assertEquals('count, should be 9', trieOne.getCount(), 9);
|
||||
assertEquals('count, should be 0', trieTwo.getCount(), 0);
|
||||
}
|
||||
|
||||
function testRemoveKeyFromTrie() {
|
||||
var trie = new goog.structs.Trie();
|
||||
trie.add('key1', 'value1');
|
||||
trie.add('key2', 'value2');
|
||||
trie.add('ke', 'value3');
|
||||
trie.add('zero', 0);
|
||||
trie.remove('key2');
|
||||
assertEquals('get "key1", should be "value1"', trie.get('key1'), 'value1');
|
||||
assertUndefined('get "key2", should be undefined', trie.get('key2'));
|
||||
trie.remove('zero');
|
||||
assertUndefined('get "zero", should be undefined', trie.get('zero'));
|
||||
trie.remove('ke');
|
||||
assertUndefined('get "ke", should be undefined', trie.get('ke'));
|
||||
assertEquals('get "key1", should be "value1"', trie.get('key1'), 'value1');
|
||||
trie.add('a', 'value4');
|
||||
assertTrue('testing internal structure, a should be a child',
|
||||
'a' in trie.childNodes_);
|
||||
trie.remove('a');
|
||||
assertFalse('testing internal structure, a should no longer be a child',
|
||||
'a' in trie.childNodes_);
|
||||
|
||||
trie.add('xyza', 'value');
|
||||
trie.remove('xyza', 'value');
|
||||
assertFalse('Should not have "x"', 'x' in trie.childNodes_);
|
||||
|
||||
trie.add('xyza', null);
|
||||
assertTrue('Should have "x"', 'x' in trie.childNodes_);
|
||||
trie.remove('xyza');
|
||||
assertFalse('Should not have "x"', 'x' in trie.childNodes_);
|
||||
|
||||
trie.add('xyza', 'value');
|
||||
trie.add('xb', 'value');
|
||||
trie.remove('xyza');
|
||||
assertTrue('get "x" should be defined', 'x' in trie.childNodes_);
|
||||
assertFalse('get "y" should be undefined',
|
||||
'y' in trie.childNodes_['x'].childNodes_);
|
||||
|
||||
trie.add('akey', 'value1');
|
||||
trie.add('akey1', 'value2');
|
||||
trie.remove('akey1');
|
||||
assertEquals('get "akey", should be "value1"', 'value1', trie.get('akey'));
|
||||
assertUndefined('get "akey1", should be undefined', trie.get('akey1'));
|
||||
}
|
||||
|
||||
function testRemoveKeyFromTrieWithNulls() {
|
||||
var trie = new goog.structs.Trie();
|
||||
trie.add('key1', null);
|
||||
trie.add('key2', 'value2');
|
||||
trie.add('ke', 'value3');
|
||||
trie.add('zero', 0);
|
||||
trie.remove('key2');
|
||||
assertEquals('get "key1", should be null', trie.get('key1'), null);
|
||||
assertUndefined('get "key2", should be undefined', trie.get('key2'));
|
||||
trie.remove('zero');
|
||||
assertUndefined('get "zero", should be undefined', trie.get('zero'));
|
||||
trie.remove('ke');
|
||||
assertUndefined('get "ke", should be undefined', trie.get('ke'));
|
||||
assertEquals('get "key1", should be null', trie.get('key1'), null);
|
||||
trie.add('a', 'value4');
|
||||
assertTrue('testing internal structure, a should be a child',
|
||||
'a' in trie.childNodes_);
|
||||
trie.remove('a');
|
||||
assertFalse('testing internal structure, a should no longer be a child',
|
||||
'a' in trie.childNodes_);
|
||||
|
||||
trie.add('xyza', null);
|
||||
trie.add('xb', 'value');
|
||||
trie.remove('xyza');
|
||||
assertTrue('Should have "x"', 'x' in trie.childNodes_);
|
||||
assertFalse('Should not have "y"',
|
||||
'y' in trie.childNodes_['x'].childNodes_);
|
||||
}
|
||||
|
||||
function testRemoveKeyException() {
|
||||
var trie = new goog.structs.Trie();
|
||||
trie.add('abcdefg', 'value');
|
||||
trie.add('abcz', 'value');
|
||||
trie.add('abc', 'value');
|
||||
|
||||
assertThrows('Remove should throw an error on removal of non-existent key',
|
||||
function() {
|
||||
trie.remove('abcdefge');
|
||||
});
|
||||
}
|
||||
|
||||
function testTrieIsEmpty() {
|
||||
var trieOne = new goog.structs.Trie();
|
||||
var trieTwo = makeTrie();
|
||||
assertTrue('isEmpty, should be empty', trieOne.isEmpty());
|
||||
assertFalse('isEmpty, should not be empty', trieTwo.isEmpty());
|
||||
trieOne.add('', 1);
|
||||
assertFalse('isEmpty, should not be empty', trieTwo.isEmpty());
|
||||
trieOne.remove('');
|
||||
assertTrue('isEmpty, should be empty', trieOne.isEmpty());
|
||||
trieOne.add('', 1);
|
||||
trieOne.add('a', 1);
|
||||
trieOne.remove('a');
|
||||
assertFalse('isEmpty, should not be empty', trieOne.isEmpty());
|
||||
trieOne.remove('');
|
||||
assertTrue('isEmpty, should be empty', trieOne.isEmpty());
|
||||
trieOne.add('', 1);
|
||||
trieOne.add('a', 1);
|
||||
trieOne.remove('');
|
||||
assertFalse('isEmpty, should not be empty', trieOne.isEmpty());
|
||||
trieOne.remove('a');
|
||||
assertTrue('isEmpty, should be empty', trieOne.isEmpty());
|
||||
}
|
||||
|
||||
function testTrieClear() {
|
||||
var trie = new goog.structs.Trie();
|
||||
trie.add('key1', 'value1');
|
||||
trie.add('key2', 'value2');
|
||||
trie.add('key3', null);
|
||||
trie.clear();
|
||||
assertUndefined('get key1, should be undefined', trie.get('key1'));
|
||||
assertUndefined('get key2, should be undefined', trie.get('key2'));
|
||||
assertUndefined('get key3, should be undefined', trie.get('key3'));
|
||||
}
|
||||
|
||||
function testTrieContainsKey() {
|
||||
var trie = makeTrie();
|
||||
assertTrue('containsKey, should contain "hello"', trie.containsKey('hello'));
|
||||
assertTrue('containsKey, should contain "hi"', trie.containsKey('hi'));
|
||||
assertTrue('containsKey, should contain ""', trie.containsKey(''));
|
||||
assertTrue('containsKey, should contain "empty value"',
|
||||
trie.containsKey('empty value'));
|
||||
assertTrue('containsKey, should contain "object"',
|
||||
trie.containsKey('object'));
|
||||
assertTrue('containsKey, should contain "zero"', trie.containsKey('zero'));
|
||||
assertTrue('containsKey, should contain "null"', trie.containsKey('null'));
|
||||
assertFalse('containsKey, should not contain "blah"',
|
||||
trie.containsKey('blah'));
|
||||
trie.remove('');
|
||||
trie.remove('hi');
|
||||
trie.remove('zero');
|
||||
trie.remove('null');
|
||||
assertFalse('containsKey, should not contain "zero"',
|
||||
trie.containsKey('zero'));
|
||||
assertFalse('containsKey, should not contain ""', trie.containsKey(''));
|
||||
assertFalse('containsKey, should not contain "hi"', trie.containsKey('hi'));
|
||||
assertFalse('containsKey, should not contain "null"',
|
||||
trie.containsKey('null'));
|
||||
}
|
||||
|
||||
function testTrieContainsPrefix() {
|
||||
|
||||
// Empty trie.
|
||||
var trie = new goog.structs.Trie();
|
||||
assertFalse('containsPrefix, should not contain ""', trie.containsPrefix(''));
|
||||
assertFalse('containsPrefix, should not contain "any"',
|
||||
trie.containsPrefix('any'));
|
||||
trie.add('key', 'value');
|
||||
assertTrue('containsPrefix, should contain ""', trie.containsPrefix(''));
|
||||
|
||||
// Non-empty trie.
|
||||
trie = makeTrie();
|
||||
assertTrue('containsPrefix, should contain ""', trie.containsPrefix(''));
|
||||
assertFalse('containsPrefix, should not contain "blah"',
|
||||
trie.containsPrefix('blah'));
|
||||
assertTrue('containsPrefix, should contain "h"', trie.containsPrefix('h'));
|
||||
assertTrue('containsPrefix, should contain "hello"',
|
||||
trie.containsPrefix('hello'));
|
||||
assertTrue('containsPrefix, should contain "hello, world"',
|
||||
trie.containsPrefix('hello, world'));
|
||||
assertFalse('containsPrefix, should not contain "hello, world!"',
|
||||
trie.containsPrefix('hello, world!'));
|
||||
assertTrue('containsPrefix, should contain "nu"',
|
||||
trie.containsPrefix('nu'));
|
||||
assertTrue('containsPrefix, should contain "null"',
|
||||
trie.containsPrefix('null'));
|
||||
assertTrue('containsPrefix, should contain "empty value"',
|
||||
trie.containsPrefix('empty value'));
|
||||
|
||||
// Remove nodes.
|
||||
trie.remove('');
|
||||
assertTrue('containsPrefix, should contain ""', trie.containsPrefix(''));
|
||||
trie.remove('hi');
|
||||
assertTrue('containsPrefix, should contain "h"', trie.containsPrefix('h'));
|
||||
assertFalse('containsPrefix, should not contain "hi"',
|
||||
trie.containsPrefix('hi'));
|
||||
trie.remove('hello');
|
||||
trie.remove('hello, world');
|
||||
assertFalse('containsPrefix, should not contain "h"',
|
||||
trie.containsPrefix('h'));
|
||||
assertFalse('containsPrefix, should not contain "hello"',
|
||||
trie.containsPrefix('hello'));
|
||||
|
||||
// Remove all nodes.
|
||||
trie.remove('empty value');
|
||||
trie.remove('zero');
|
||||
trie.remove('object');
|
||||
trie.remove('null');
|
||||
trie.remove('world');
|
||||
assertFalse('containsPrefix, should not contain ""',
|
||||
trie.containsPrefix(''));
|
||||
assertFalse('containsPrefix, should not contain "h"',
|
||||
trie.containsPrefix('h'));
|
||||
assertFalse('containsPrefix, should not contain "hi"',
|
||||
trie.containsPrefix('hi'));
|
||||
|
||||
// Add some new nodes.
|
||||
trie.add('hi', 'value');
|
||||
trie.add('null', 'value');
|
||||
assertTrue('containsPrefix, should contain ""', trie.containsPrefix(''));
|
||||
assertTrue('containsPrefix, should contain "h"', trie.containsPrefix('h'));
|
||||
assertTrue('containsPrefix, should contain "hi"', trie.containsPrefix('hi'));
|
||||
assertFalse('containsPrefix, should not contain "hello"',
|
||||
trie.containsPrefix('hello'));
|
||||
assertFalse('containsPrefix, should not contain "zero"',
|
||||
trie.containsPrefix('zero'));
|
||||
|
||||
// Clear the trie.
|
||||
trie.clear();
|
||||
assertFalse('containsPrefix, should not contain ""',
|
||||
trie.containsPrefix(''));
|
||||
assertFalse('containsPrefix, should not contain "h"',
|
||||
trie.containsPrefix('h'));
|
||||
assertFalse('containsPrefix, should not contain "hi"',
|
||||
trie.containsPrefix('hi'));
|
||||
}
|
||||
|
||||
function testTrieContainsValue() {
|
||||
var trie = makeTrie();
|
||||
assertTrue('containsValue, should be true, should contain 1',
|
||||
trie.containsValue(1));
|
||||
assertTrue('containsValue, should be true, should contain "howdy"',
|
||||
trie.containsValue('howdy'));
|
||||
assertTrue('containsValue, should be true, should contain ""',
|
||||
trie.containsValue(''));
|
||||
assertTrue('containsValue, should be true, should contain 0',
|
||||
trie.containsValue(0));
|
||||
assertTrue('containsValue, should be true, should contain null',
|
||||
trie.containsValue(null));
|
||||
assertTrue('containsValue, should be true, should ' +
|
||||
'contain "an empty string key"',
|
||||
trie.containsValue('an empty string key'));
|
||||
assertFalse('containsValue, should be false, should not contain "blah"',
|
||||
trie.containsValue('blah'));
|
||||
trie.remove('empty value');
|
||||
trie.remove('zero');
|
||||
assertFalse('containsValue, should be false, should not contain 0',
|
||||
trie.containsValue(0));
|
||||
assertFalse('containsValue, should be false, should not contain ""',
|
||||
trie.containsValue(''));
|
||||
}
|
||||
|
||||
function testTrieHandlingOfEmptyStrings() {
|
||||
var trie = new goog.structs.Trie();
|
||||
assertEquals('get, should be undefined', trie.get(''), undefined);
|
||||
assertFalse('containsValue, should be false', trie.containsValue(''));
|
||||
assertFalse('containsKey, should be false', trie.containsKey(''));
|
||||
trie.add('', 'test');
|
||||
trie.add('test2', '');
|
||||
assertTrue('containsValue, should be true', trie.containsValue(''));
|
||||
assertTrue('containsKey, should be true', trie.containsKey(''));
|
||||
assertEquals('get, should be "test"', trie.get(''), 'test');
|
||||
assertEquals('get, should be ""', trie.get('test2'), '');
|
||||
trie.remove('');
|
||||
trie.remove('test2');
|
||||
assertEquals('get, should be undefined', trie.get(''), undefined);
|
||||
assertFalse('containsValue, should be false', trie.containsValue(''));
|
||||
assertFalse('containsKey, should be false', trie.containsKey(''));
|
||||
}
|
||||
|
||||
function testPrefixOptionOnGetKeys() {
|
||||
var trie = new goog.structs.Trie();
|
||||
trie.add('abcdefg', 'one');
|
||||
trie.add('abcdefghijk', 'two');
|
||||
trie.add('abcde', 'three');
|
||||
trie.add('abcq', null);
|
||||
trie.add('abc', 'four');
|
||||
trie.add('xyz', 'five');
|
||||
assertEquals('getKeys, should be 1', trie.getKeys('xy').length, 1);
|
||||
assertEquals('getKeys, should be 1', trie.getKeys('xyz').length, 1);
|
||||
assertEquals('getKeys, should be 1', trie.getKeys('x').length, 1);
|
||||
assertEquals('getKeys, should be 4', trie.getKeys('abc').length, 5);
|
||||
assertEquals('getKeys, should be 2', trie.getKeys('abcdef').length, 2);
|
||||
assertEquals('getKeys, should be 0', trie.getKeys('abcdefgi').length, 0);
|
||||
}
|
||||
|
||||
function testGetKeyAndPrefixes() {
|
||||
var trie = makeTrie();
|
||||
// Note: trie has one of its keys as ''
|
||||
assertEquals('getKeyAndPrefixes, should be 2',
|
||||
2,
|
||||
goog.object.getCount(trie.getKeyAndPrefixes('world')));
|
||||
assertEquals('getKeyAndPrefixes, should be 2',
|
||||
2,
|
||||
goog.object.getCount(trie.getKeyAndPrefixes('hello')));
|
||||
assertEquals('getKeyAndPrefixes, should be 2',
|
||||
2,
|
||||
goog.object.getCount(trie.getKeyAndPrefixes('hello,')));
|
||||
assertEquals('getKeyAndPrefixes, should be 3',
|
||||
3,
|
||||
goog.object.getCount(trie.getKeyAndPrefixes('hello, world')));
|
||||
assertEquals('getKeyAndPrefixes, should be 1',
|
||||
1,
|
||||
goog.object.getCount(trie.getKeyAndPrefixes('hell')));
|
||||
}
|
||||
|
||||
function testGetKeyAndPrefixesStartIndex() {
|
||||
var trie = new goog.structs.Trie();
|
||||
trie.add('abcdefg', 'one');
|
||||
trie.add('bcdefg', 'two');
|
||||
trie.add('abcdefghijk', 'three');
|
||||
trie.add('abcde', 'four');
|
||||
trie.add('abcq', null);
|
||||
trie.add('q', null);
|
||||
trie.add('abc', 'five');
|
||||
trie.add('xyz', 'six');
|
||||
assertEquals('getKeyAndPrefixes, should be 3',
|
||||
3,
|
||||
goog.object.getCount(trie.getKeyAndPrefixes('abcdefg', 0)));
|
||||
assertEquals('getKeyAndPrefixes, should be 1',
|
||||
1,
|
||||
goog.object.getCount(trie.getKeyAndPrefixes('abcdefg', 1)));
|
||||
assertEquals('getKeyAndPrefixes, should be 1',
|
||||
1,
|
||||
goog.object.getCount(trie.getKeyAndPrefixes('abcq', 3)));
|
||||
assertEquals('getKeyAndPrefixes, should be 0',
|
||||
0,
|
||||
goog.object.getCount(trie.getKeyAndPrefixes('abcd', 3)));
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
// Copyright 2014 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 Common code for weak collections.
|
||||
*
|
||||
* The helpers in this file are used for the shim implementations of
|
||||
* {@code goog.structs.weak.Map} and {@code goog.structs.weak.Set}, for browsers
|
||||
* that do not support ECMAScript 6 native WeakMap and WeakSet.
|
||||
*
|
||||
* IMPORTANT CAVEAT: On browsers that do not provide native WeakMap and WeakSet
|
||||
* implementations, these data structure are only partially weak, and CAN LEAK
|
||||
* MEMORY. Specifically, if a key is no longer held, the key-value pair (in the
|
||||
* case of Map) and some internal metadata (in the case of Set), can be garbage
|
||||
* collected; however, if a key is still held when a Map or Set is no longer
|
||||
* held, the value and metadata will not be collected.
|
||||
*
|
||||
* RECOMMENDATIONS: If the lifetime of the weak collection is expected to be
|
||||
* shorter than that of its keys, the keys should be explicitly removed from the
|
||||
* collection when they are disposed. If this is not possible, this library may
|
||||
* be inappopriate for the application.
|
||||
*
|
||||
* BROWSER COMPATIBILITY: This library is compatible with browsers with a
|
||||
* correct implementation of Object.defineProperty (IE9+, FF4+, SF5.1+, CH5+,
|
||||
* OP12+, etc).
|
||||
* @see goog.structs.weak.SUPPORTED_BROWSER
|
||||
* @see http://kangax.github.io/compat-table/es5/#Object.defineProperty
|
||||
*
|
||||
* @package
|
||||
*/
|
||||
|
||||
|
||||
goog.provide('goog.structs.weak');
|
||||
|
||||
goog.require('goog.userAgent');
|
||||
|
||||
|
||||
/**
|
||||
* Whether this browser supports weak collections, using either the native or
|
||||
* shim implementation.
|
||||
* @const
|
||||
*/
|
||||
// Only test for shim, since ES6 native WeakMap/Set imply ES5 shim dependencies
|
||||
goog.structs.weak.SUPPORTED_BROWSER = Object.defineProperty &&
|
||||
// IE<9 and Safari<5.1 cannot defineProperty on some objects
|
||||
!(goog.userAgent.IE && !goog.userAgent.isVersionOrHigher('9')) &&
|
||||
!(goog.userAgent.SAFARI && !goog.userAgent.isVersionOrHigher('534.48.3'));
|
||||
|
||||
|
||||
/**
|
||||
* Whether to use the browser's native WeakMap.
|
||||
* @const
|
||||
*/
|
||||
goog.structs.weak.USE_NATIVE_WEAKMAP = 'WeakMap' in goog.global &&
|
||||
// Firefox<24 WeakMap disallows some objects as keys
|
||||
// See https://github.com/Polymer/WeakMap/issues/3
|
||||
!(goog.userAgent.GECKO && !goog.userAgent.isVersionOrHigher('24'));
|
||||
|
||||
|
||||
/**
|
||||
* Whether to use the browser's native WeakSet.
|
||||
* @const
|
||||
*/
|
||||
goog.structs.weak.USE_NATIVE_WEAKSET = 'WeakSet' in goog.global;
|
||||
|
||||
|
||||
/** @const */
|
||||
goog.structs.weak.WEAKREFS_PROPERTY_NAME = '__shimWeakrefs__';
|
||||
|
||||
|
||||
/**
|
||||
* Counter used to generate unique ID for shim.
|
||||
* @private
|
||||
*/
|
||||
goog.structs.weak.counter_ = 0;
|
||||
|
||||
|
||||
/**
|
||||
* Generate a unique ID for shim.
|
||||
* @return {string}
|
||||
*/
|
||||
goog.structs.weak.generateId = function() {
|
||||
return (Math.random() * 1e9 >>> 0) + '' +
|
||||
(goog.structs.weak.counter_++ % 1e9);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Checks that the key is an extensible object, otherwise throws an Error.
|
||||
* @param {*} key The key.
|
||||
*/
|
||||
goog.structs.weak.checkKeyType = function(key) {
|
||||
if (!goog.isObject(key)) {
|
||||
throw TypeError('Invalid value used in weak collection');
|
||||
}
|
||||
if (Object.isExtensible && !Object.isExtensible(key)) {
|
||||
throw TypeError('Unsupported non-extensible object used as weak map key');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Adds a key-value pair to the collection with the given ID. Helper for shim
|
||||
* implementations of Map#set and Set#add.
|
||||
* @param {string} id The unique ID of the shim weak collection.
|
||||
* @param {*} key The key.
|
||||
* @param {*} value value to add.
|
||||
*/
|
||||
goog.structs.weak.set = function(id, key, value) {
|
||||
goog.structs.weak.checkKeyType(key);
|
||||
if (!key.hasOwnProperty(goog.structs.weak.WEAKREFS_PROPERTY_NAME)) {
|
||||
// Use defineProperty to make property non-enumerable
|
||||
Object.defineProperty(/** @type {!Object} */(key),
|
||||
goog.structs.weak.WEAKREFS_PROPERTY_NAME, {value: {}});
|
||||
}
|
||||
key[goog.structs.weak.WEAKREFS_PROPERTY_NAME][id] = value;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether the collection with the given ID contains the given
|
||||
* key. Helper for shim implementations of Map#containsKey and Set#contains.
|
||||
* @param {string} id The unique ID of the shim weak collection.
|
||||
* @param {*} key The key to check for.
|
||||
* @return {boolean}
|
||||
*/
|
||||
goog.structs.weak.has = function(id, key) {
|
||||
goog.structs.weak.checkKeyType(key);
|
||||
return key.hasOwnProperty(goog.structs.weak.WEAKREFS_PROPERTY_NAME) ?
|
||||
id in key[goog.structs.weak.WEAKREFS_PROPERTY_NAME] :
|
||||
false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Removes a key-value pair based on the key. Helper for shim implementations of
|
||||
* Map#remove and Set#remove.
|
||||
* @param {string} id The unique ID of the shim weak collection.
|
||||
* @param {*} key The key to remove.
|
||||
* @return {boolean} Whether object was removed.
|
||||
*/
|
||||
goog.structs.weak.remove = function(id, key) {
|
||||
if (goog.structs.weak.has(id, key)) {
|
||||
delete key[goog.structs.weak.WEAKREFS_PROPERTY_NAME][id];
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
@@ -0,0 +1,75 @@
|
||||
// Copyright 2014 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 Tests for {@code goog.structs.weak}. set, has, and remove are
|
||||
* exercised in {@code goog.structs.weak.MapTest} and
|
||||
* {@code goog.structs.weak.SetTest}.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
goog.provide('goog.structs.weakTest');
|
||||
goog.setTestOnly('goog.structs.weakTest');
|
||||
|
||||
goog.require('goog.array');
|
||||
goog.require('goog.structs.weak');
|
||||
goog.require('goog.testing.jsunit');
|
||||
|
||||
|
||||
function shouldRunTests() {
|
||||
return goog.structs.weak.SUPPORTED_BROWSER;
|
||||
}
|
||||
|
||||
|
||||
function testGenerateId() {
|
||||
assertNotEquals(goog.structs.weak.generateId(),
|
||||
goog.structs.weak.generateId());
|
||||
}
|
||||
|
||||
|
||||
function testCheckKeyTypeValidObject() {
|
||||
var validKeys = [{}, [], document.body, /RegExp/, goog.nullFunction];
|
||||
goog.array.forEach(validKeys, function(key) {
|
||||
// no error
|
||||
goog.structs.weak.checkKeyType(key);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function testCheckKeyTypePrimitive() {
|
||||
var primitiveKeys = ['test', 1, true, null, undefined];
|
||||
goog.array.forEach(primitiveKeys, function(key) {
|
||||
assertThrows(function() {
|
||||
goog.structs.weak.checkKeyType(key);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function testCheckKeyTypeNonExtensibleObject() {
|
||||
var sealedObj = {}, frozenObj = {}, preventExtensionsObj = {};
|
||||
Object.seal(sealedObj);
|
||||
Object.freeze(frozenObj);
|
||||
Object.preventExtensions(preventExtensionsObj);
|
||||
assertThrows(function() {
|
||||
goog.structs.weak.checkKeyType(sealedObj);
|
||||
});
|
||||
assertThrows(function() {
|
||||
goog.structs.weak.checkKeyType(frozenObj);
|
||||
});
|
||||
assertThrows(function() {
|
||||
goog.structs.weak.checkKeyType(preventExtensionsObj);
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user