SetCnt.mjs

/**
 * Set-like data structure built using Map with elements counter.
 * Allows adding multiple occurrences of the same element.
 */
export class SetCnt {
  #map = new Map();
  
  /**
   * Creates a new SetCnt instance.
   * @param {Iterable} [iter] - An iterable object to initialize the set with.
   */
  constructor(iter) {
    for (const el of iter ?? []) {
      this.add(el);
    }
  }

  /**
   * Adds an element to the set.
   * Increments the counter for the element.
   * @param {*} el - The element to add.
   * @returns {SetCnt} The SetCnt instance.
   */
  add(el) {
    const cnt = this.cnt(el);
    this.#map.set(el, cnt + 1);
    return this;
  }

  /**
   * Checks if an element exists in the set.
   * @param {*} el - The element to check.
   * @returns {boolean} True if the element exists, false otherwise.
   */
  has(el) {
    return this.#map.has(el);
  }

  /**
   * Removes one occurrence of an element from the set.
   * If the count reaches 0, the element is removed completely.
   * @param {*} el - The element to remove.
   * @returns {boolean} True if the element was removed (or count decremented), false if it didn't exist.
   */
  delete(el) {
    if (!this.#map.has(el)) return false;
    const cnt = this.cnt(el);
    if (cnt === 1) return this.deleteAll(el);
    this.#map.set(el, cnt - 1);
    return true;
  }
  
  /**
   * Removes all occurrences of an element from the set.
   * @param {*} el - The element to remove.
   * @returns {boolean} True if the element was removed, false otherwise.
   */
  deleteAll(el) {
    this.#map.delete(el);
    return true;
  }

  /**
   * Gets the number of occurrences of an element.
   * @param {*} el - The element to check.
   * @returns {number} The count of the element.
   */
  cnt(el) {
    return this.#map.get(el) ?? 0;
  }
  
  /**
   * Returns an iterator over the elements in the set.
   * Each element is yielded as many times as its count.
   * @returns {Iterator} An iterator over the elements.
   */
  *[Symbol.iterator]() {
    for (const el of this.#map) {
      yield el;
    }
  }

  /**
   * Returns the SetCnt instance itself (for compatibility with Map-like interfaces).
   * @returns {SetCnt} The SetCnt instance.
   */
  entries() {
    return this;
  }

  /**
   * Returns an iterator over the unique elements in the set.
   * @returns {Iterator} An iterator over the unique elements.
   */
  *values() {
    for (const [el, cnt] of this.#map) {
      yield el;
    }
  }

  /**
   * Returns an iterator over the counts of the elements.
   * @returns {Iterator} An iterator over the counts.
   */
  *keys() {
    for (const [el, cnt] of this.#map) {
      yield cnt;
    }
  }
}