UNPKG

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
// 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.