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

const slice = Array.prototype.slice;
const filter = Array.prototype.filter;

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

export function isSuperset(a, b) {
  let [, set] = toSet(a);
  let [, subset] = toSet(b);
  for (var elem of subset) {
    if (!set.has(elem)) {
      return false;
    }
  }
  return true;
}

export function union(a, b) {
  let [isSet, setA] = toSet(a);
  let [, setB] = toSet(b);
  const union = new Set(setA);
  for (var elem of setB) {
    union.add(elem);
  }
  return isSet ? union : Array.from(union);
}

// Find the member in a full set that is in the tags (where typeOf prefix is
// not available to find this via Model#typeOf)
export function member(tags, fullSet) {
  let set = intersection(tags, fullSet);
  if (set.size > 1) {
    throw Error(`Not one value of {${fullSet}} in {${Array.from(tags)}}`);
  } else if (set.size === 0) {
    return null;
  }
  return Array.from(set)[0];
}

export function intersection(a, b) {
  let [isSet, setA] = toSet(a);
  let [, setB] = toSet(b);
  const intersection = new Set();
  for (var elem of setB) {
    if (setA.has(elem)) {
      intersection.add(elem);
    }
  }
  return isSet ? intersection : Array.from(intersection);
}

export function symmetricDifference(a, b) {
  let [isSet, setA] = toSet(a);
  let [, setB] = toSet(b);
  var difference = new Set(setA);
  for (var elem of setB) {
    if (difference.has(elem)) {
      difference.delete(elem);
    } else {
      difference.add(elem);
    }
  }
  return isSet ? difference : Array.from(difference);
}

export function difference(a, b) {
  let [isSet, setA] = toSet(a);
  let [, setB] = toSet(b);
  var difference = new Set(setA);
  for (var 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) {
  let [isSet, set] = toSet(array);
  return isSet ? array : Array.from(set);
}

// wtf does this actually do
export function without(array) {
  let [isSet, set] = toSet(array);
  for (var i = 1; i < arguments.length; i++) {
    set.delete(arguments[i]);
  }
  return isSet ? set : Array.from(set);
}