Edd Mann Developer

Implementing a Compound Set in TypeScript

Since being introduced to Advent of Code, one feature missing from JavaScript that I have seen available in other languages (such as Python and Clojure) are Sets which hande Compound data-types. So as to focus each puzzle solution on the problem at hand and not re-invent the wheel I decided to implement a CompoundSet data-structure which would fill this void.

JavaScript does include the ability to create Sets with primitive data-types. However, in certain use-cases (such as storing X-Y coordinates) it is useful to be able to store more complex data-types (such as Tuples). Although this is techincally possible, the gotcha is that equality is based on reference as opposed to value semantics. So storing the Compound data-type within the Set will be valid, except checking for existence will require the exact same value reference to be supplied.

There is a proposal to introduce Records and Tuples in a future version of JavaScript which will alleviate this problem - but until that is introduced I decided to create a CompoundSet implementation backed by a native Map like so:

class CompoundSet<T> {
  private set: Map<string, T>;

  constructor(initial: T[] = []) {
    this.set = new Map(initial.map(val => [this.toKey(val), val]));
  }

  has(val: T): boolean {
    return this.set.has(this.toKey(val));
  }

  add(val: T): this {
    this.set.set(this.toKey(val), val);
    return this;
  }

  delete(val: T): this {
    this.set.delete(this.toKey(val));
    return this;
  }

  [Symbol.iterator]() {
    return this.set.values();
  }

  get size() {
    return this.set.size;
  }

  private toKey(val: T): string {
    return JSON.stringify(val);
  }
}

This implementation is not exhaustive and does not provide all the functionality that a native Set provides. It does however include enough of the behaviour to be a drop-in replacement for solving daily puzzles I have been working through. Encoding the value as a JSON string provides us with the primitive-type required for the Map key - which we can subsequently use to check for existence (by value). Using an internal Map we are able to ensure that we persist the original value, which can be used when iterating over the Set.

I have found this data-structure to be very useful whilst completing my first Advent of Code calendar in 2020. In the future I hope to explore how we could use a similiar means to provide a CompoundMap implementation.