All files / collections/src indexedSet.ts

95% Statements 19/20
100% Branches 3/3
90.9% Functions 10/11
94.73% Lines 18/19

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 1242x 2x                                                   20x 2x           20x 20x       6x 5x   6x       1x       2x                                                               2x                                         2x 2x           8x       8x       17x     20x    
import { clear, deleteAt, deleteFirst, insertAt, move } from "@benneq/array";
import { AbstractSet } from "./abstractSet";
 
/**
 * A {@link Set} implementation that provides index-based access.
 *
 * Similar to an {@link Array} that only allows unique elements.
 *
 * @example
 * ```ts
 * const indexedSet = new IndexedSet();
 * indexedSet.add(1);
 * indexedSet.add(3);
 * indexedSet.add(2);
 * indexedSet.add(1);
 *
 * console.log(indexedSet); // 1, 3, 2
 *
 * indexedSet.set(0, 4);
 * console.log(indexedSet); // 4, 1, 3, 2
 *
 * indexedSet.set(3, 1);
 * console.log(indexedSet); // 4, 3, 1, 2
 * ```
 *
 * @typeParam T - the {@link IndexedSet} element type
 */
export class IndexedSet<T> extends AbstractSet<T> {
  readonly #values;
 
  /**
   * @param values - optional {@link Iterable} of values for initialization
   */
  constructor(values?: Iterable<T>) {
    super();
    this.#values = [...new Set(values)];
  }
 
  add(value: T): this {
    if (!this.has(value)) {
      this.#values.push(value);
    }
    return this;
  }
 
  clear(): void {
    clear(this.#values);
  }
 
  delete(value: T): boolean {
    return deleteFirst(value)(this.#values);
  }
 
  /**
   * Deletes the element at the given `index`.
   *
   * @mutation
   *
   * @example
   * Remove value at index 1
   * ```ts
   * const indexedSet = new IndexedSet([1, 2, 3]);
   *
   * indexedSet.deleteAt(1);
   *
   * console.log([...indexedSet.values()]); // [1, 3]
   * ```
   *
   * @param index - the index to delete
   * @returns `true` if the {@link IndexedSet} was modified, otherwise `false`
   */
  deleteAt(index: number, deleteCount?: number): boolean {
    return deleteAt(index, deleteCount)(this.#values);
  }
 
  /**
   * Returns the value at the specified `index`.
   *
   * @param index - the index of the element to get
   * @returns the element at the specified `index`, or `undefined` if out of bounds
   */
  at(index: number): T | undefined {
    return this.#values.at(index);
  }
 
  /**
   * Sets the value at the specified `index`. If the value already exists, it
   * will be moved to the new index, otherwise it will be inserted at the index.
   *
   * @example
   * Set value
   * ```ts
   * const indexedSet = new IndexedSet([1, 2, 3]);
   *
   * indexedSet.set(0, 3);
   *
   * console.log([...indexedSet.values()]); // [3, 1, 2]
   * ```
   *
   * @param index - the index where to set the value
   * @param value - the value to set
   */
  set(index: number, value: T): void {
    const sourceIndex = this.#values.indexOf(value);
    (sourceIndex < 0 ? insertAt(index, value) : move(sourceIndex, index))(
      this.#values
    );
  }
 
  has(value: T): boolean {
    return this.#values.includes(value);
  }
 
  get size(): number {
    return this.#values.length;
  }
 
  values(): IterableIterator<T> {
    return this.#values[Symbol.iterator]();
  }
 
  [Symbol.toStringTag] = "IndexedSet";
}