superset
Version:
A library that provides the missing Set methods such as map and filter.
306 lines (272 loc) • 10.9 kB
JavaScript
;
/**
* The callback type used by the `map()` method.
*
* @callback mapper
* @param {*} element The element being processed
* @param {*} key The element being processed again, since sets don't have keys
* @param {SuperSet} setObj The set instance being worked on
* @returns {*} The processed item
*/
/**
* The callback used by methods `filter()`, `some()` and `find()`.
*
* @callback tester
* @param {*} element The element being tested
* @param {*} key The element being tested again, since sets don't have keys
* @param {SuperSet} setObj The set instance being worked on
* @returns {Boolean} The result of the test
*/
/**
* The callback type used by the `reduce()` method.
*
* @callback reductionProcessor
* @param {*} accumulator The current accumulator value
* @param {*} item The element being processed
* @param {*} key The element being processed again, since sets don't have keys
* @param {SuperSet} setObj The set instance being worked on
* @returns {*} The new value of the accumulator
*/
/**
* Generator that implements `map()`. Passed to SuperSet/Set constructor for efficiency from the actual `map()` method.
*
* @param {Set} setObj The set object to be transformed
* @param {mapper} transform Transform function to be applied to each element in the set
* @returns {Generator} A stream of transformed items.
*/
function* mapGen(setObj, transform) {
for (const itm of setObj)
yield transform(itm, itm, setObj);
}
/**
* Generator that implements `filter()`. Passed to SuperSet/Set constructor for efficiency from the actual `filter()`
* method.
*
* @param {Set} setObj The set object to be filtered
* @param {tester} filter Filter function to be applied to each element in the set
* @returns {Generator} A stream of filtered items.
*/
function* filterGen(setObj, filter) {
for (const itm of setObj) {
if (filter(itm, itm, setObj))
yield itm;
}
}
/**
* Generator that implements set subtraction. Used by the `subtract()` method and other helpers.
* @param {Set} set1 The set to be subtracted from.
* @param {Set} set2 The set that is going to be subtracted.
* @returns {Generator} A stream of items in the resultant set.
*/
function* subtractGen(set1, set2) {
for (const itm of set1) {
if (!set2.has(itm))
yield itm;
}
}
/**
* Generator that simply yields all items from the provided sets.
* @param {...Set} sets The sets to be chained
* @returns {Generator} A stream of items from all provided sets.
*/
function* chain() {
for (const setObj of arguments)
yield* setObj;
}
/**
* Generator that implements the XOR operation between two sets: yielding only items existing in one set.
* @param {Set} set1 The first set for XOR.
* @param {Set} set2 The second set for XOR.
* @returns {Generator} A stream of items that only occur in one of the provided two sets.
*/
function* xorGen(set1, set2) {
yield* chain(
subtractGen(set1, set2),
subtractGen(set2, set1)
);
}
/**
* A more capable `Set` type that implements essential collection methods such as `map()`, `filter()` and `reduce()` in
* addition to basic set methods such as `union()`, `isSubsetOf()` etc. extending the built-in `Set` type.
*/
class SuperSet extends Set {
/**
* Applies the provided transforming `func` to all elements in the set and returns a new set with the return values.
*
* @param {mapper} func The transform function for each element in the set.
* @param {Object} [thisArg] The context object to be bound to the provided transform `func`.
* @returns {SuperSet} The set of transformed items.
*/
map(func, thisArg) {
return new SuperSet(mapGen(this, func.bind(thisArg)));
}
/**
* Applies the provided filter `func` to all elements in the set and returns a new set with the returned, filtered
* elements.
*
* @param {tester} func The filter/test function for each element in the set.
* @param {Object} [thisArg] The context object to be bound to the provided test `func`.
* @returns {SuperSet} The set of items that passes the test/filter.
*/
filter(func, thisArg) {
return new SuperSet(filterGen(this, func.bind(thisArg)));
}
/**
* Merges the set with all provided sets and returns a new set as the result. Equivalent of the mathematical "union"
* operation.
*
* @param {...Set} sets The sets to be merged/unioned with.
* @returns {SuperSet} The resultant union set.
*/
union() {
const sets = Array.from(arguments);
sets.unshift(this);
return new SuperSet(chain.apply(undefined, sets));
}
/**
* Applies the provided `func` to all items in the set. Returns `true` if all items result in a "truthy" value,
* `false` otherwise.
*
* @param {tester} func The "test" function that will be applied to each item in the set.
* @param {Object} [thisArg] The context object to be bound to the provided transform `func`.
* @returns {boolean} `true` if all items "pass the test", `false` otherwise.
*/
every(func, thisArg) {
const check = func.bind(thisArg);
for (const itm of this) {
if (!check(itm, itm, this))
return false;
}
return true;
}
/**
* Applies the provided `func` to all items in the set and returns the first one that makes it return a "truthy"
* value.
*
* @param {tester} func The "test" function to be applied to items in the set.
* @param {Object} [thisArg] The context object to be bound to the provided transform `func`.
* @returns {*} The first item from the set that "passes the test".
*/
find(func, thisArg) {
const check = func.bind(thisArg);
for (const itm of this) {
if (check(itm, itm, this))
return itm;
}
}
/**
* Equivalent of `Array.prototype.join` for sets. Coerces and concatenates all items in the set using the provided
* `separator`.
*
* @param {string} separator The "glue" to join items in the set with.
* @returns {string} The resultant string from the concatenation.
*/
join(separator) {
return Array.from(this).join(separator);
}
/**
* Reduces all items in the set using the provided reducing `func`. Equivalent of `Array.prototype.reduce`.
* @param {reductionProcessor} func The reducing function.
* @param {*} [initialValue] The initial value for the accumulator. When `reduce` is called on a non-empty set, the
* first value of the set is used as the default value.
* @returns {*} The result of the whole reduce operation.
*/
reduce(func, initialValue) {
/* eslint-disable no-magic-numbers */
if (arguments.length < 2 && this.size === 0)
throw new TypeError("An initial value is required when using an empty set.");
const iterator = this[Symbol.iterator]();
let result = arguments.length === 1 ? iterator.next().value : initialValue;
for (const itm of iterator)
result = func(result, itm, itm, this);
return result;
}
/**
* Applies the provided `func` to all items in the set. Returns `true` if any one of results in a "truthy" value,
* `false` otherwise.
*
* @param {tester} func The "test" function that will be applied to each item in the set.
* @param {Object} [thisArg] The context object to be bound to the provided transform `func`.
* @returns {boolean} `true` if one of the items in the set "passes the test", `false` otherwise.
*/
some(func, thisArg) {
const check = func.bind(thisArg);
for (const itm of this) {
if (check(itm, itm, this))
return true;
}
return false;
}
/**
* Returns `true` if the current set instance is a subset of the provided set instance. Mathematical equivalent of
* the subset test.
*
* @param {Set} otherSetObj The set that is expected to be the superset of the current set.
* @returns {boolean} `true` if `otherSetObj` is a superset, `false` otherwise.
*/
isSubsetOf(otherSetObj) {
return this.every(otherSetObj.has, otherSetObj);
}
/**
* Returns `true` if provided set is mathematically equal to the provided set.
*
* @param {Set} otherSetObj The set to be tested against for equality.
* @returns {boolean} `true` if two sets are equal, `false` otherwise.
*/
equals(otherSetObj) {
return this.size === otherSetObj.size && this.isSubsetOf(otherSetObj);
}
/**
* Returns a new set instance which contains the items from the intersection of the current set instance and the
* provided set. Mathematical equivalent of set intersection.
*
* @param {Set} otherSetObj The set instance to be intersected with.
* @returns {SuperSet} The intersection set of the two sets.
*/
intersect(otherSetObj) {
return this.filter(otherSetObj.has, otherSetObj);
}
/**
* Returns a new set instance which contains the items that are not in the provided set. Mathematical equivalent of
* set subtraction.
*
* @param {Set} otherSetObj The set to be subtracted.
* @returns {SuperSet} The subtraction set for the A - B set operation where A is the current set instance.
*/
diff(otherSetObj) {
return new SuperSet(subtractGen(this, otherSetObj));
}
/**
* Adds all items from the provided iterable to the current set.
*
* @param {Iterable} iterable The iterable containing the items to be added to the current set.
* @returns {SuperSet} The current set instance, but updated in-place.
*/
update(iterable) {
for (const itm of iterable)
this.add(itm);
return this;
}
/**
* Returns a new set instance which contains the items that exists only in one of the sets provided. Mathematical
* equivalent of set XOR operation.
*
* @param {Set} otherSetObj The set to be used in the XOR operation.
* @returns {SuperSet} The resultant set for the A ^ B set operation where A is the current set instance.
*/
symmetricDiff(otherSetObj) {
return new SuperSet(xorGen(this, otherSetObj));
}
/**
* The discard() method deletes the iterable elements from the set and return the updated elements.
*
* @param {Iterable} iterable It contains iterable elements to be deleted from the current set.
* @returns {SuperSet} It returns the current state of the set after the deleted elements.
*/
discard(iterable) {
for (const itm of iterable)
this.delete(itm);
return this;
}
}
module.exports = SuperSet;