buckets-js
Version:
Buckets is a complete, fully tested and documented data structure library written in pure JavaScript.
1,484 lines (1,350 loc) • 92 kB
JavaScript
// buckets
// version: 1.98.2
// (c) 2013 - 2016 Mauricio Santos
// https://github.com/mauriciosantos/Buckets-JS
(function (root, factory) {
// UMD (Universal Module Definition) https://github.com/umdjs/umd
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module unless amdModuleId is set
define([], factory);
} else if (typeof exports === 'object') {
// Node. Does not work with strict CommonJS, but
// only CommonJS-like environments that support module.exports,
// like Node.
module.exports = factory();
} else {
// Browser globals (root is window)
root.buckets = factory();
}
}(this, function () {
'use strict';
/**
* Top level namespace for Buckets,
* a JavaScript data structure library.
* @name buckets
*/
var buckets = {};
/**
* Default function to compare element order.
* @function
* @private
*/
buckets.defaultCompare = function (a, b) {
if (a < b) {
return -1;
}
if (a === b) {
return 0;
}
return 1;
};
/**
* Default function to test equality.
* @function
* @private
*/
buckets.defaultEquals = function (a, b) {
return a === b;
};
/**
* Default function to convert an object to a string.
* @function
* @private
*/
buckets.defaultToString = function (item) {
if (item === null) {
return 'BUCKETS_NULL';
}
if (buckets.isUndefined(item)) {
return 'BUCKETS_UNDEFINED';
}
if (buckets.isString(item)) {
return item;
}
return item.toString();
};
/**
* Checks if the given argument is a function.
* @function
* @private
*/
buckets.isFunction = function (func) {
return (typeof func) === 'function';
};
/**
* Checks if the given argument is undefined.
* @function
* @private
*/
buckets.isUndefined = function (obj) {
return obj === undefined;
};
/**
* Checks if the given argument is a string.
* @function
* @private
*/
buckets.isString = function (obj) {
return Object.prototype.toString.call(obj) === '[object String]';
};
/**
* Reverses a compare function.
* @function
* @private
*/
buckets.reverseCompareFunction = function (compareFunction) {
if (!buckets.isFunction(compareFunction)) {
return function (a, b) {
if (a < b) {
return 1;
}
if (a === b) {
return 0;
}
return -1;
};
}
return function (d, v) {
return compareFunction(d, v) * -1;
};
};
/**
* Returns an equal function given a compare function.
* @function
* @private
*/
buckets.compareToEquals = function (compareFunction) {
return function (a, b) {
return compareFunction(a, b) === 0;
};
};
/**
* @namespace Contains various functions for manipulating arrays.
*/
buckets.arrays = {};
/**
* Returns the index of the first occurrence of the specified item
* within the specified array.
* @param {*} array The array.
* @param {*} item The element to search for.
* @param {function(Object,Object):boolean=} equalsFunction Optional function to
* check equality between two elements. Receives two arguments and returns true if they are equal.
* @return {number} The index of the first occurrence of the specified element
* or -1 if not found.
*/
buckets.arrays.indexOf = function (array, item, equalsFunction) {
var equals = equalsFunction || buckets.defaultEquals,
length = array.length,
i;
for (i = 0; i < length; i += 1) {
if (equals(array[i], item)) {
return i;
}
}
return -1;
};
/**
* Returns the index of the last occurrence of the specified element
* within the specified array.
* @param {*} array The array.
* @param {Object} item The element to search for.
* @param {function(Object,Object):boolean=} equalsFunction Optional function to
* check equality between two elements. Receives two arguments and returns true if they are equal.
* @return {number} The index of the last occurrence of the specified element
* within the specified array or -1 if not found.
*/
buckets.arrays.lastIndexOf = function (array, item, equalsFunction) {
var equals = equalsFunction || buckets.defaultEquals,
length = array.length,
i;
for (i = length - 1; i >= 0; i -= 1) {
if (equals(array[i], item)) {
return i;
}
}
return -1;
};
/**
* Returns true if the array contains the specified element.
* @param {*} array The array.
* @param {Object} item The element to search for.
* @param {function(Object,Object):boolean=} equalsFunction Optional function to
* check equality between two elements. Receives two arguments and returns true if they are equal.
* @return {boolean} True if the specified array contains the specified element.
*/
buckets.arrays.contains = function (array, item, equalsFunction) {
return buckets.arrays.indexOf(array, item, equalsFunction) >= 0;
};
/**
* Removes the first ocurrence of the specified element from the specified array.
* @param {*} array The array.
* @param {*} item The element to remove.
* @param {function(Object,Object):boolean=} equalsFunction Optional function to
* check equality between two elements. Receives two arguments and returns true if they are equal.
* @return {boolean} True If the array changed after this call.
*/
buckets.arrays.remove = function (array, item, equalsFunction) {
var index = buckets.arrays.indexOf(array, item, equalsFunction);
if (index < 0) {
return false;
}
array.splice(index, 1);
return true;
};
/**
* Returns the number of elements in the array equal
* to the specified element.
* @param {Array} array The array.
* @param {Object} item The element.
* @param {function(Object,Object):boolean=} equalsFunction Optional function to
* check equality between two elements. Receives two arguments and returns true if they are equal.
* @return {number} The number of elements in the specified array.
* equal to the specified item.
*/
buckets.arrays.frequency = function (array, item, equalsFunction) {
var equals = equalsFunction || buckets.defaultEquals,
length = array.length,
freq = 0,
i;
for (i = 0; i < length; i += 1) {
if (equals(array[i], item)) {
freq += 1;
}
}
return freq;
};
/**
* Returns true if the provided arrays are equal.
* Two arrays are considered equal if both contain the same number
* of elements and all corresponding pairs of elements
* are equal and are in the same order.
* @param {Array} array1
* @param {Array} array2
* @param {function(Object,Object):boolean=} equalsFunction Optional function to
* check equality between two elements. Receives two arguments and returns true if they are equal.
* @return {boolean} True if the two arrays are equal.
*/
buckets.arrays.equals = function (array1, array2, equalsFunction) {
var equals = equalsFunction || buckets.defaultEquals,
length = array1.length,
i;
if (array1.length !== array2.length) {
return false;
}
for (i = 0; i < length; i += 1) {
if (!equals(array1[i], array2[i])) {
return false;
}
}
return true;
};
/**
* Returns a shallow copy of the specified array.
* @param {*} array The array to copy.
* @return {Array} A copy of the specified array.
*/
buckets.arrays.copy = function (array) {
return array.concat();
};
/**
* Swaps the elements at the specified positions in the specified array.
* @param {Array} array The array.
* @param {number} i The index of the first element.
* @param {number} j The index of second element.
* @return {boolean} True if the array is defined and the indexes are valid.
*/
buckets.arrays.swap = function (array, i, j) {
var temp;
if (i < 0 || i >= array.length || j < 0 || j >= array.length) {
return false;
}
temp = array[i];
array[i] = array[j];
array[j] = temp;
return true;
};
/**
* Executes the provided function once per element present in the array.
* @param {Array} array The array.
* @param {function(Object):*} callback Function to execute,
* invoked with an element as argument. To break the iteration you can
* optionally return false in the callback.
*/
buckets.arrays.forEach = function (array, callback) {
var lenght = array.length,
i;
for (i = 0; i < lenght; i += 1) {
if (callback(array[i]) === false) {
return;
}
}
};
/**
* Creates an empty bag.
* @class <p>A bag is a special kind of set in which members are
* allowed to appear more than once.</p>
* <p>If the inserted elements are custom objects, a function
* that maps elements to unique strings must be provided at construction time.</p>
* <p>Example:</p>
* <pre>
* function petToUniqueString(pet) {
* return pet.type + ' ' + pet.name;
* }
* </pre>
*
* @constructor
* @param {function(Object):string=} toStrFunction Optional function
* to convert elements to unique strings. If the elements aren't strings or if toString()
* is not appropriate, a custom function which receives an object and returns a
* unique string must be provided.
*/
buckets.Bag = function (toStrFunction) {
/**
* @exports bag as buckets.Bag
* @private
*/
var bag = {},
// Function to convert elements to unique strings.
toStrF = toStrFunction || buckets.defaultToString,
// Underlying Storage
dictionary = new buckets.Dictionary(toStrF),
// Number of elements in the bag, including duplicates.
nElements = 0;
/**
* Adds nCopies of the specified element to the bag.
* @param {Object} element Element to add.
* @param {number=} nCopies The number of copies to add, if this argument is
* undefined 1 copy is added.
* @return {boolean} True unless element is undefined.
*/
bag.add = function (element, nCopies) {
var node;
if (isNaN(nCopies) || buckets.isUndefined(nCopies)) {
nCopies = 1;
}
if (buckets.isUndefined(element) || nCopies <= 0) {
return false;
}
if (!bag.contains(element)) {
node = {
value: element,
copies: nCopies
};
dictionary.set(element, node);
} else {
dictionary.get(element).copies += nCopies;
}
nElements += nCopies;
return true;
};
/**
* Counts the number of copies of the specified element in the bag.
* @param {Object} element The element to search for.
* @return {number} The number of copies of the element, 0 if not found.
*/
bag.count = function (element) {
if (!bag.contains(element)) {
return 0;
}
return dictionary.get(element).copies;
};
/**
* Returns true if the bag contains the specified element.
* @param {Object} element Element to search for.
* @return {boolean} True if the bag contains the specified element,
* false otherwise.
*/
bag.contains = function (element) {
return dictionary.containsKey(element);
};
/**
* Removes nCopies of the specified element from the bag.
* If the number of copies to remove is greater than the actual number
* of copies in the bag, all copies are removed.
* @param {Object} element Element to remove.
* @param {number=} nCopies The number of copies to remove, if this argument is
* undefined 1 copy is removed.
* @return {boolean} True if at least 1 copy was removed.
*/
bag.remove = function (element, nCopies) {
var node;
if (isNaN(nCopies) || buckets.isUndefined(nCopies)) {
nCopies = 1;
}
if (buckets.isUndefined(element) || nCopies <= 0) {
return false;
}
if (!bag.contains(element)) {
return false;
}
node = dictionary.get(element);
if (nCopies > node.copies) {
nElements -= node.copies;
} else {
nElements -= nCopies;
}
node.copies -= nCopies;
if (node.copies <= 0) {
dictionary.remove(element);
}
return true;
};
/**
* Returns an array containing all the elements in the bag in no particular order,
* including multiple copies.
* @return {Array} An array containing all the elements in the bag.
*/
bag.toArray = function () {
var a = [],
values = dictionary.values(),
vl = values.length,
node,
element,
copies,
i,
j;
for (i = 0; i < vl; i += 1) {
node = values[i];
element = node.value;
copies = node.copies;
for (j = 0; j < copies; j += 1) {
a.push(element);
}
}
return a;
};
/**
* Returns a set of unique elements in the bag.
* @return {buckets.Set} A set of unique elements in the bag.
*/
bag.toSet = function () {
var set = new buckets.Set(toStrF),
elements = dictionary.values(),
l = elements.length,
i;
for (i = 0; i < l; i += 1) {
set.add(elements[i].value);
}
return set;
};
/**
* Executes the provided function once per element
* present in the bag, including multiple copies.
* @param {function(Object):*} callback Function to execute, it's
* invoked with an element as argument. To break the iteration you can
* optionally return false in the callback.
*/
bag.forEach = function (callback) {
dictionary.forEach(function (k, v) {
var value = v.value,
copies = v.copies,
i;
for (i = 0; i < copies; i += 1) {
if (callback(value) === false) {
return false;
}
}
return true;
});
};
/**
* Returns the number of elements in the bag, including duplicates.
* @return {number} The number of elements in the bag.
*/
bag.size = function () {
return nElements;
};
/**
* Returns true if the bag contains no elements.
* @return {boolean} True if the bag contains no elements.
*/
bag.isEmpty = function () {
return nElements === 0;
};
/**
* Removes all the elements from the bag.
*/
bag.clear = function () {
nElements = 0;
dictionary.clear();
};
/**
* Returns true if the bag is equal to another bag.
* Two bags are equal if they have the same elements and
* same number of copies per element.
* @param {buckets.Bag} other The other bag.
* @return {boolean} True if the bag is equal to the given bag.
*/
bag.equals = function (other) {
var isEqual;
if (buckets.isUndefined(other) || typeof other.toSet !== 'function') {
return false;
}
if (bag.size() !== other.size()) {
return false;
}
isEqual = true;
other.forEach(function (element) {
isEqual = (bag.count(element) === other.count(element));
return isEqual;
});
return isEqual;
};
return bag;
};
/**
* Creates an empty binary search tree.
* @class <p> Binary search trees keep their elements in sorted order, so that
* lookup and other operations can use the principle of binary search. In a BST
* the element in any node is larger than the elements in the node's
* left sub-tree and smaller than the elements in the node's right sub-tree.</p>
* <p>If the inserted elements are custom objects, a compare function must
* be provided at construction time, otherwise the <=, === and >= operators are
* used to compare elements.</p>
* <p>Example:</p>
* <pre>
* function compare(a, b) {
* if (a is less than b by some ordering criterion) {
* return -1;
* } if (a is greater than b by the ordering criterion) {
* return 1;
* }
* // a must be equal to b
* return 0;
* }
* </pre>
* @constructor
* @param {function(Object,Object):number=} compareFunction Optional
* function used to compare two elements. Must return a negative integer,
* zero, or a positive integer as the first argument is less than, equal to,
* or greater than the second.
*/
buckets.BSTree = function (compareFunction) {
/**
* @exports tree as buckets.BSTree
* @private
*/
var tree = {},
// Function to compare elements.
compare = compareFunction || buckets.defaultCompare,
// Number of elements in the tree.
nElements = 0,
// The root node of the tree.
root;
// Returns the sub-node containing the specified element or undefined.
function searchNode(root, element) {
var node = root,
cmp;
while (node !== undefined && cmp !== 0) {
cmp = compare(element, node.element);
if (cmp < 0) {
node = node.leftCh;
} else if (cmp > 0) {
node = node.rightCh;
}
}
return node;
}
// Returns the sub-node containing the minimum element or undefined.
function minimumAux(root) {
var node = root;
while (node.leftCh !== undefined) {
node = node.leftCh;
}
return node;
}
/**
* Inserts the specified element into the tree if it's not already present.
* @param {Object} element The element to insert.
* @return {boolean} True if the tree didn't already contain the element.
*/
tree.add = function (element) {
if (buckets.isUndefined(element)) {
return false;
}
/**
* @private
*/
function insertNode(node) {
var position = root,
parent,
cmp;
while (position !== undefined) {
cmp = compare(node.element, position.element);
if (cmp === 0) {
return undefined;
}
if (cmp < 0) {
parent = position;
position = position.leftCh;
} else {
parent = position;
position = position.rightCh;
}
}
node.parent = parent;
if (parent === undefined) {
// tree is empty
root = node;
} else if (compare(node.element, parent.element) < 0) {
parent.leftCh = node;
} else {
parent.rightCh = node;
}
return node;
}
var node = {
element: element,
leftCh: undefined,
rightCh: undefined,
parent: undefined
};
if (insertNode(node) !== undefined) {
nElements += 1;
return true;
}
return false;
};
/**
* Removes all the elements from the tree.
*/
tree.clear = function () {
root = undefined;
nElements = 0;
};
/**
* Returns true if the tree contains no elements.
* @return {boolean} True if the tree contains no elements.
*/
tree.isEmpty = function () {
return nElements === 0;
};
/**
* Returns the number of elements in the tree.
* @return {number} The number of elements in the tree.
*/
tree.size = function () {
return nElements;
};
/**
* Returns true if the tree contains the specified element.
* @param {Object} element Element to search for.
* @return {boolean} True if the tree contains the element,
* false otherwise.
*/
tree.contains = function (element) {
if (buckets.isUndefined(element)) {
return false;
}
return searchNode(root, element) !== undefined;
};
/**
* Removes the specified element from the tree.
* @return {boolean} True if the tree contained the specified element.
*/
tree.remove = function (element) {
var node;
function transplant(n1, n2) {
if (n1.parent === undefined) {
root = n2;
} else if (n1 === n1.parent.leftCh) {
n1.parent.leftCh = n2;
} else {
n1.parent.rightCh = n2;
}
if (n2 !== undefined) {
n2.parent = n1.parent;
}
}
function removeNode(node) {
if (node.leftCh === undefined) {
transplant(node, node.rightCh);
} else if (node.rightCh === undefined) {
transplant(node, node.leftCh);
} else {
var y = minimumAux(node.rightCh);
if (y.parent !== node) {
transplant(y, y.rightCh);
y.rightCh = node.rightCh;
y.rightCh.parent = y;
}
transplant(node, y);
y.leftCh = node.leftCh;
y.leftCh.parent = y;
}
}
node = searchNode(root, element);
if (node === undefined) {
return false;
}
removeNode(node);
nElements -= 1;
return true;
};
/**
* Executes the provided function once per element present in the tree in in-order.
* @param {function(Object):*} callback Function to execute, invoked with an element as
* argument. To break the iteration you can optionally return false in the callback.
*/
tree.inorderTraversal = function (callback) {
function inorderRecursive(node, callback, signal) {
if (node === undefined || signal.stop) {
return;
}
inorderRecursive(node.leftCh, callback, signal);
if (signal.stop) {
return;
}
signal.stop = callback(node.element) === false;
if (signal.stop) {
return;
}
inorderRecursive(node.rightCh, callback, signal);
}
inorderRecursive(root, callback, {
stop: false
});
};
/**
* Executes the provided function once per element present in the tree in pre-order.
* @param {function(Object):*} callback Function to execute, invoked with an element as
* argument. To break the iteration you can optionally return false in the callback.
*/
tree.preorderTraversal = function (callback) {
function preorderRecursive(node, callback, signal) {
if (node === undefined || signal.stop) {
return;
}
signal.stop = callback(node.element) === false;
if (signal.stop) {
return;
}
preorderRecursive(node.leftCh, callback, signal);
if (signal.stop) {
return;
}
preorderRecursive(node.rightCh, callback, signal);
}
preorderRecursive(root, callback, {
stop: false
});
};
/**
* Executes the provided function once per element present in the tree in post-order.
* @param {function(Object):*} callback Function to execute, invoked with an element as
* argument. To break the iteration you can optionally return false in the callback.
*/
tree.postorderTraversal = function (callback) {
function postorderRecursive(node, callback, signal) {
if (node === undefined || signal.stop) {
return;
}
postorderRecursive(node.leftCh, callback, signal);
if (signal.stop) {
return;
}
postorderRecursive(node.rightCh, callback, signal);
if (signal.stop) {
return;
}
signal.stop = callback(node.element) === false;
}
postorderRecursive(root, callback, {
stop: false
});
};
/**
* Executes the provided function once per element present in the tree in level-order.
* @param {function(Object):*} callback Function to execute, invoked with an element as
* argument. To break the iteration you can optionally return false in the callback.
*/
tree.levelTraversal = function (callback) {
function levelAux(node, callback) {
var queue = buckets.Queue();
if (node !== undefined) {
queue.enqueue(node);
}
while (!queue.isEmpty()) {
node = queue.dequeue();
if (callback(node.element) === false) {
return;
}
if (node.leftCh !== undefined) {
queue.enqueue(node.leftCh);
}
if (node.rightCh !== undefined) {
queue.enqueue(node.rightCh);
}
}
}
levelAux(root, callback);
};
/**
* Returns the minimum element of the tree.
* @return {*} The minimum element of the tree or undefined if the tree
* is empty.
*/
tree.minimum = function () {
if (tree.isEmpty()) {
return undefined;
}
return minimumAux(root).element;
};
/**
* Returns the maximum element of the tree.
* @return {*} The maximum element of the tree or undefined if the tree
* is empty.
*/
tree.maximum = function () {
function maximumAux(node) {
while (node.rightCh !== undefined) {
node = node.rightCh;
}
return node;
}
if (tree.isEmpty()) {
return undefined;
}
return maximumAux(root).element;
};
/**
* Executes the provided function once per element present in the tree in in-order.
* Equivalent to inorderTraversal.
* @param {function(Object):*} callback Function to execute, it's
* invoked with an element argument. To break the iteration you can
* optionally return false in the callback.
*/
tree.forEach = function (callback) {
tree.inorderTraversal(callback);
};
/**
* Returns an array containing all the elements in the tree in in-order.
* @return {Array} An array containing all the elements in the tree in in-order.
*/
tree.toArray = function () {
var array = [];
tree.inorderTraversal(function (element) {
array.push(element);
});
return array;
};
/**
* Returns the height of the tree.
* @return {number} The height of the tree or -1 if it's empty.
*/
tree.height = function () {
function heightAux(node) {
if (node === undefined) {
return -1;
}
return Math.max(heightAux(node.leftCh), heightAux(node.rightCh)) + 1;
}
function heightRecursive(node) {
if (node === undefined) {
return -1;
}
return Math.max(heightAux(node.leftCh), heightAux(node.rightCh)) + 1;
}
return heightRecursive(root);
};
/**
* Returns true if the tree is equal to another tree.
* Two trees are equal if they have the same elements.
* @param {buckets.BSTree} other The other tree.
* @return {boolean} True if the tree is equal to the given tree.
*/
tree.equals = function (other) {
var isEqual;
if (buckets.isUndefined(other) || typeof other.levelTraversal !== 'function') {
return false;
}
if (tree.size() !== other.size()) {
return false;
}
isEqual = true;
other.forEach(function (element) {
isEqual = tree.contains(element);
return isEqual;
});
return isEqual;
};
return tree;
};
/**
* Creates an empty dictionary.
* @class <p>Dictionaries map keys to values, each key can map to at most one value.
* This implementation accepts any kind of objects as keys.</p>
*
* <p>If the keys are custom objects, a function that converts keys to unique
* strings must be provided at construction time.</p>
* <p>Example:</p>
* <pre>
* function petToString(pet) {
* return pet.name;
* }
* </pre>
* @constructor
* @param {function(Object):string=} toStrFunction Optional function used
* to convert keys to unique strings. If the keys aren't strings or if toString()
* is not appropriate, a custom function which receives a key and returns a
* unique string must be provided.
*/
buckets.Dictionary = function (toStrFunction) {
/**
* @exports dictionary as buckets.Dictionary
* @private
*/
var dictionary = {},
// Object holding the key-value pairs.
table = {},
// Number of keys in the dictionary.
nElements = 0,
// Function to convert keys unique to strings.
toStr = toStrFunction || buckets.defaultToString,
// Special string to prefix keys and avoid name collisions with existing properties.
keyPrefix = '/$ ';
/**
* Returns the value associated with the specified key in the dictionary.
* @param {Object} key The key.
* @return {*} The mapped value or
* undefined if the dictionary contains no mapping for the provided key.
*/
dictionary.get = function (key) {
var pair = table[keyPrefix + toStr(key)];
if (buckets.isUndefined(pair)) {
return undefined;
}
return pair.value;
};
/**
* Associates the specified value with the specified key in the dictionary.
* If the dictionary previously contained a mapping for the key, the old
* value is replaced by the specified value.
* @param {Object} key The key.
* @param {Object} value Value to be mapped with the specified key.
* @return {*} Previous value associated with the provided key, or undefined if
* there was no mapping for the key or the key/value is undefined.
*/
dictionary.set = function (key, value) {
var ret, k, previousElement;
if (buckets.isUndefined(key) || buckets.isUndefined(value)) {
return undefined;
}
k = keyPrefix + toStr(key);
previousElement = table[k];
if (buckets.isUndefined(previousElement)) {
nElements += 1;
ret = undefined;
} else {
ret = previousElement.value;
}
table[k] = {
key: key,
value: value
};
return ret;
};
/**
* Removes the value associated with the specified key from the dictionary if it exists.
* @param {Object} key The key.
* @return {*} Removed value associated with the specified key, or undefined if
* there was no mapping for the key.
*/
dictionary.remove = function (key) {
var k = keyPrefix + toStr(key),
previousElement = table[k];
if (!buckets.isUndefined(previousElement)) {
delete table[k];
nElements -= 1;
return previousElement.value;
}
return undefined;
};
/**
* Returns an array containing all the keys in the dictionary.
* @return {Array} An array containing all the keys in the dictionary.
*/
dictionary.keys = function () {
var array = [],
name;
for (name in table) {
if (Object.prototype.hasOwnProperty.call(table, name)) {
array.push(table[name].key);
}
}
return array;
};
/**
* Returns an array containing all the values in the dictionary.
* @return {Array} An array containing all the values in the dictionary.
*/
dictionary.values = function () {
var array = [],
name;
for (name in table) {
if (Object.prototype.hasOwnProperty.call(table, name)) {
array.push(table[name].value);
}
}
return array;
};
/**
* Executes the provided function once per key-value pair
* present in the dictionary.
* @param {function(Object,Object):*} callback Function to execute. Receives
* 2 arguments: key and value. To break the iteration you can
* optionally return false inside the callback.
*/
dictionary.forEach = function (callback) {
var name, pair, ret;
for (name in table) {
if (Object.prototype.hasOwnProperty.call(table, name)) {
pair = table[name];
ret = callback(pair.key, pair.value);
if (ret === false) {
return;
}
}
}
};
/**
* Returns true if the dictionary contains a mapping for the specified key.
* @param {Object} key The key.
* @return {boolean} True if the dictionary contains a mapping for the
* specified key.
*/
dictionary.containsKey = function (key) {
return !buckets.isUndefined(dictionary.get(key));
};
/**
* Removes all keys and values from the dictionary.
* @this {buckets.Dictionary}
*/
dictionary.clear = function () {
table = {};
nElements = 0;
};
/**
* Returns the number of key-value pais in the dictionary.
* @return {number} The number of key-value mappings in the dictionary.
*/
dictionary.size = function () {
return nElements;
};
/**
* Returns true if the dictionary contains no keys.
* @return {boolean} True if this dictionary contains no mappings.
*/
dictionary.isEmpty = function () {
return nElements <= 0;
};
/**
* Returns true if the dictionary is equal to another dictionary.
* Two dictionaries are equal if they have the same key-value pairs.
* @param {buckets.Dictionary} other The other dictionary.
* @param {function(Object,Object):boolean=} equalsFunction Optional
* function to check if two values are equal. If the values in the dictionaries
* are custom objects you should provide a custom equals function, otherwise
* the === operator is used to check equality between values.
* @return {boolean} True if the dictionary is equal to the given dictionary.
*/
dictionary.equals = function (other, equalsFunction) {
var eqf, isEqual;
if (buckets.isUndefined(other) || typeof other.keys !== 'function') {
return false;
}
if (dictionary.size() !== other.size()) {
return false;
}
eqf = equalsFunction || buckets.defaultEquals;
isEqual = true;
other.forEach(function (k, v) {
isEqual = eqf(dictionary.get(k), v);
return isEqual;
});
return isEqual;
};
return dictionary;
};
/**
* Creates an empty binary heap.
* @class
* <p>A heap is a binary tree that maintains the heap property:
* Every node is less than or equal to each of its children.
* This implementation uses an array as the underlying storage.</p>
* <p>If the inserted elements are custom objects, a compare function must be provided
* at construction time, otherwise the <=, === and >= operators are
* used to compare elements.</p>
* <p>Example:</p>
* <pre>
* function compare(a, b) {
* if (a is less than b by some ordering criterion) {
* return -1;
* } if (a is greater than b by the ordering criterion) {
* return 1;
* }
* // a must be equal to b
* return 0;
* }
* </pre>
*
* <p>To create a Max-Heap (greater elements on top) you can a provide a
* reverse compare function.</p>
* <p>Example:</p>
*
* <pre>
* function reverseCompare(a, b) {
* if (a is less than b by some ordering criterion) {
* return 1;
* } if (a is greater than b by the ordering criterion) {
* return -1;
* }
* // a must be equal to b
* return 0;
* }
* </pre>
*
* @constructor
* @param {function(Object,Object):number=} compareFunction Optional
* function used to compare two elements. Must return a negative integer,
* zero, or a positive integer as the first argument is less than, equal to,
* or greater than the second.
*/
buckets.Heap = function (compareFunction) {
/**
* @exports heap as buckets.Heap
* @private
*/
var heap = {},
// Array used to store the elements of the heap.
data = [],
// Function used to compare elements.
compare = compareFunction || buckets.defaultCompare;
// Moves the node at the given index up to its proper place in the heap.
function siftUp(index) {
var parent;
// Returns the index of the parent of the node at the given index.
function parentIndex(nodeIndex) {
return Math.floor((nodeIndex - 1) / 2);
}
parent = parentIndex(index);
while (index > 0 && compare(data[parent], data[index]) > 0) {
buckets.arrays.swap(data, parent, index);
index = parent;
parent = parentIndex(index);
}
}
// Moves the node at the given index down to its proper place in the heap.
function siftDown(nodeIndex) {
var min;
// Returns the index of the left child of the node at the given index.
function leftChildIndex(nodeIndex) {
return (2 * nodeIndex) + 1;
}
// Returns the index of the right child of the node at the given index.
function rightChildIndex(nodeIndex) {
return (2 * nodeIndex) + 2;
}
// Returns the index of the smaller child node if it exists, -1 otherwise.
function minIndex(leftChild, rightChild) {
if (rightChild >= data.length) {
if (leftChild >= data.length) {
return -1;
}
return leftChild;
}
if (compare(data[leftChild], data[rightChild]) <= 0) {
return leftChild;
}
return rightChild;
}
// Minimum child index
min = minIndex(leftChildIndex(nodeIndex), rightChildIndex(nodeIndex));
while (min >= 0 && compare(data[nodeIndex], data[min]) > 0) {
buckets.arrays.swap(data, min, nodeIndex);
nodeIndex = min;
min = minIndex(leftChildIndex(nodeIndex), rightChildIndex(nodeIndex));
}
}
/**
* Retrieves but does not remove the root (minimum) element of the heap.
* @return {*} The value at the root of the heap. Returns undefined if the
* heap is empty.
*/
heap.peek = function () {
if (data.length > 0) {
return data[0];
}
return undefined;
};
/**
* Adds the given element into the heap.
* @param {*} element The element.
* @return True if the element was added or false if it is undefined.
*/
heap.add = function (element) {
if (buckets.isUndefined(element)) {
return undefined;
}
data.push(element);
siftUp(data.length - 1);
return true;
};
/**
* Retrieves and removes the root (minimum) element of the heap.
* @return {*} The removed element or
* undefined if the heap is empty.
*/
heap.removeRoot = function () {
var obj;
if (data.length > 0) {
obj = data[0];
data[0] = data[data.length - 1];
data.splice(data.length - 1, 1);
if (data.length > 0) {
siftDown(0);
}
return obj;
}
return undefined;
};
/**
* Returns true if the heap contains the specified element.
* @param {Object} element Element to search for.
* @return {boolean} True if the Heap contains the specified element, false
* otherwise.
*/
heap.contains = function (element) {
var equF = buckets.compareToEquals(compare);
return buckets.arrays.contains(data, element, equF);
};
/**
* Returns the number of elements in the heap.
* @return {number} The number of elements in the heap.
*/
heap.size = function () {
return data.length;
};
/**
* Checks if the heap is empty.
* @return {boolean} True if the heap contains no elements; false
* otherwise.
*/
heap.isEmpty = function () {
return data.length <= 0;
};
/**
* Removes all the elements from the heap.
*/
heap.clear = function () {
data.length = 0;
};
/**
* Executes the provided function once per element present in the heap in
* no particular order.
* @param {function(Object):*} callback Function to execute,
* invoked with an element as argument. To break the iteration you can
* optionally return false.
*/
heap.forEach = function (callback) {
buckets.arrays.forEach(data, callback);
};
/**
* Returns an array containing all the elements in the heap in no
* particular order.
* @return {Array.<*>} An array containing all the elements in the heap
* in no particular order.
*/
heap.toArray = function () {
return buckets.arrays.copy(data);
};
/**
* Returns true if the binary heap is equal to another heap.
* Two heaps are equal if they have the same elements.
* @param {buckets.Heap} other The other heap.
* @return {boolean} True if the heap is equal to the given heap.
*/
heap.equals = function (other) {
var thisArray, otherArray, eqF;
if (buckets.isUndefined(other) || typeof other.removeRoot !== 'function') {
return false;
}
if (heap.size() !== other.size()) {
return false;
}
thisArray = heap.toArray();
otherArray = other.toArray();
eqF = buckets.compareToEquals(compare);
thisArray.sort(compare);
otherArray.sort(compare);
return buckets.arrays.equals(thisArray, otherArray, eqF);
};
return heap;
};
/**
* Creates an empty Linked List.
* @class A linked list is a sequence of items arranged one after
* another. The size is not fixed and it can grow or shrink
* on demand. One of the main benefits of a linked list is that
* you can add or remove elements at both ends in constant time.
* One disadvantage of a linked list against an array is
* that it doesn’t provide constant time random access.
* @constructor
*/
buckets.LinkedList = function () {
/**
* @exports list as buckets.LinkedList
* @private
*/
var list = {},
// Number of elements in the list
nElements = 0,
// First node in the list
firstNode,
// Last node in the list
lastNode;
// Returns the node at the specified index.
function nodeAtIndex(index) {
var node, i;
if (index < 0 || index >= nElements) {
return undefined;
}
if (index === (nElements - 1)) {
return lastNode;
}
node = firstNode;
for (i = 0; i < index; i += 1) {
node = node.next;
}
return node;
}
/**
* Adds an element to the list.
* @param {Object} item Element to be added.
* @param {number=} index Optional index to add the element. If no index is specified
* the element is added to the end of the list.
* @return {boolean} True if the element was added or false if the index is invalid
* or if the element is undefined.
*/
list.add = function (item, index) {
var newNode, prev;
if (buckets.isUndefined(index)) {
index = nElements;
}
if (index < 0 || index > nElements || buckets.isUndefined(item)) {
return false;
}
newNode = {
element: item,
next: undefined
};
if (nElements === 0) {
// First node in the list.
firstNode = newNode;
lastNode = newNode;
} else if (index === nElements) {
// Insert at the end.
lastNode.next = newNode;
lastNode = newNode;
} else if (index === 0) {
// Change first node.