set_utils.js

/**
 * @module set_utils
 * @description A set of utilities for performing set operations on arrays (as if they were sets).
 *    All the methods will also take sets.
 */

// See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set
// These versions can also take arrays will return the same type as they are given (sets or
// arrays).

export function toSet(value) {
  return value instanceof Set ? [true, value] : [false, new Set(value)];
}

/**
 * Returns an array with elements in both of the arrays or sets.
 *
 * @param {Array|Set} A the first array or set
 * @param {Array|Set} B the second array or set
 * @returns {Array|Set} an array or set with unique values only (matching the type of the argument).
 */
export function intersection(a, b) {
  const [isSet, setA] = toSet(a);
  const [, setB] = toSet(b);
  const intersection = new Set();
  for (let elem of setB) {
    if (setA.has(elem)) {
      intersection.add(elem);
    }
  }
  return isSet ? intersection : Array.from(intersection);
}

/**
 * Returns an array with elements in one of the two sets, but not both.
 *
 * @param {Array|Set} A the first array (a set is OK but this method won't do anything)
 * @param {Array|Set} B the second array (a set is OK but this method won't do anything)
 * @returns {Array|Set} an array or set with unique values only (matching the type of the argument).
 */
export function difference(a, b) {
  const [isSet, setA] = toSet(a);
  const [, setB] = toSet(b);
  const difference = new Set(setA);
  for (let elem of setB) {
    difference.delete(elem);
  }
  return isSet ? difference : Array.from(difference);
}

/**
 * Returns an array with only the unique values in the source array.
 *
 * @param {Array|Set} input an array (a set is OK but this method won't do anything)
 * @returns {Array|Set} an array or set with unique values only (matching the type of the argument).
 */
export function unique(array) {
  const [isSet, set] = toSet(array);
  return isSet ? array : Array.from(set);
}