UNPKG

buckets-js

Version:

Buckets is a complete, fully tested and documented data structure library written in pure JavaScript.

228 lines (213 loc) 6.92 kB
/** * 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; };