Please support this book: buy it or donate

32. Sets (Set)



Before ES6, JavaScript didn’t have a data structure for sets. Instead, two work-around were used:

  • The keys of objects as sets of strings.
  • Arrays as sets of arbitrary values (e.g., checking if an element is in a set via .includes()).
    ECMAScript 6 has the data structure Set, which works for arbitrary values.

32.1. Using Sets

32.1.1. Creating Sets

There are three common ways of creating Sets.

First, you can use the constructor without any parameters to create an empty Set:

  1. const emptySet = new Set();
  2. assert.equal(emptySet.size, 0);

Second, you can pass an iterable (e.g. an Array) to the constructor. The iterated values become elements of the new Set:

  1. const set = new Set(['red', 'green', 'blue']);

Third, the .add() method adds elements to a Set and is chainable:

  1. const set = new Set()
  2. .add('red')
  3. .add('green')
  4. .add('blue');

32.1.2. Adding, removing, checking inclusion

.add() adds an element to a Set. .has() checks if an element is included in a Set. .delete() removes an element from a Set.

  1. const set = new Set();
  2. set.add('red');
  3. assert.equal(set.has('red'), true);
  4. assert.equal(set.delete('red'), true); // there was a deletion
  5. assert.equal(set.has('red'), false);

32.1.3. Determining the size of a Set and clearing it

.size contains the number of elements in a Set. .clear() removes all elements of a Set.

  1. const set = new Set()
  2. .add('foo')
  3. .add('bar')
  4. ;
  5. assert.equal(set.size, 2)
  6. set.clear();
  7. assert.equal(set.size, 0)

32.1.4. Iterating over a set

Sets are iterable and the for-of loop works as you’d expect:

  1. const set = new Set(['red', 'green', 'blue']);
  2. for (const x of set) {
  3. console.log(x);
  4. }
  5. // Output:
  6. // 'red'
  7. // 'green'
  8. // 'blue'

As you can see, Sets preserve iteration order. That is, elements are always iterated over in the order in which they were added.

Spreading () into Arrays works with iterables and thus lets you convert a Set to an Array:

  1. const set = new Set(['red', 'green', 'blue']);
  2. const arr = [...set]; // ['red', 'green', 'blue']
32.1.4.1. Removing duplicates from Arrays and strings

Converting an Array to a Set and back, removes duplicates from the Array:

  1. assert.deepEqual(
  2. [...new Set([1, 2, 1, 2, 3, 3, 3])],
  3. [1, 2, 3]);

Strings are also iterable and therefore can be used as parameters for new Set():

  1. assert.equal(
  2. [...new Set('ababccc')].join(''),
  3. 'abc');

32.2. Comparing Set elements

As with Maps, elements are compared similarly to ===, with the exception of NaN being equal to itself.

  1. > const set = new Set([NaN]);
  2. > set.size
  3. 1
  4. > set.has(NaN)
  5. true

Adding an element a second time has no effect:

  1. > const set = new Set();
  2. > set.add('foo');
  3. > set.size
  4. 1
  5. > set.add('foo');
  6. > set.size
  7. 1

Similarly to ===, two different objects are never considered equal (which can’t currently be customized):

  1. > const set = new Set();
  2. > set.add({});
  3. > set.size
  4. 1
  5. > set.add({});
  6. > set.size
  7. 2

32.3. Missing Set operations

Sets are missing several common operations. They can usually be implemented by:

  • Converting Sets to Arrays (via spreading ()).
  • Performing the operation.
  • Converting the result back to a Set.

32.3.1. Mapping

  1. const set = new Set([1, 2, 3]);
  2. const mappedSet = new Set([...set].map(x => x * 2));
  3. // Convert mappedSet to an Array to check what’s inside it
  4. assert.deepEqual([...mappedSet], [2, 4, 6]);

32.3.2. Filtering

  1. const set = new Set([1, 2, 3, 4, 5]);
  2. const filteredSet = new Set([...set].filter(x => (x % 2) === 0));
  3. assert.deepEqual([...filteredSet], [2, 4]);

32.3.3. Union

Union (ab): create a Set that contains the elements of both Set a and Set b.

  1. const a = new Set([1,2,3]);
  2. const b = new Set([4,3,2]);
  3. // Use spreading to concatenate two iterables
  4. const union = new Set([...a, ...b]);
  5. assert.deepEqual([...union], [1, 2, 3, 4]);

32.3.4. Intersection

Intersection (ab): create a Set that contains those elements of Set a that are also in Set b.

  1. const a = new Set([1,2,3]);
  2. const b = new Set([4,3,2]);
  3. const intersection = new Set(
  4. [...a].filter(x => b.has(x)));
  5. assert.deepEqual([...intersection], [2, 3]);

32.3.5. Difference

Difference (a \ b): create a Set that contains those elements of Set a that are not in Set b. This operation is also sometimes called minus (-).

  1. const a = new Set([1,2,3]);
  2. const b = new Set([4,3,2]);
  3. const difference = new Set(
  4. [...a].filter(x => !b.has(x)));
  5. assert.deepEqual([...difference], [1]);

32.4. Quick reference: Set<T>

32.4.1. Constructor

  • new Set<T>(values?: Iterable<T>) [ES6]

If you don’t provide the parameter values then an empty Set is created. If you do then the iterated values are added as elements to the Set. For example:

  1. const set = new Set(['red', 'green', 'blue']);

32.4.2. Set<T>.prototype: single Set elements

  • .add(value: T): this [ES6]

Adds value to this Set. This method returns this, which means that it can be chained.

  1. const set = new Set(['red', 'green']);
  2. set.add('blue');
  3. assert.deepEqual([...set], ['red', 'green', 'blue']);
  • .delete(value: T): boolean [ES6]

Removes value from this Set.

  1. const set = new Set(['red', 'green', 'blue']);
  2. assert.equal(set.delete('red'), true); // there was a deletion
  3. assert.deepEqual([...set], ['green', 'blue']);
  • .has(value: T): boolean [ES6]

Checks whether value is in this Set.

  1. const set = new Set(['red', 'green']);
  2. assert.equal(set.has('red'), true);
  3. assert.equal(set.has('blue'), false);

32.4.3. Set<T>.prototype: all Set elements

  • get .size: number [ES6]

Returns how many elements there are in this Set.

  1. const set = new Set(['red', 'green', 'blue']);
  2. assert.equal(set.size, 3);
  • .clear(): void [ES6]

Removes all elements from this Set.

  1. const set = new Set(['red', 'green', 'blue']);
  2. assert.equal(set.size, 3);
  3. set.clear();
  4. assert.equal(set.size, 0);

32.4.4. Set<T>.prototype: iterating and looping

  • .values(): Iterable<T> [ES6]

Returns an iterable over all elements of this Set.

  1. const set = new Set(['red', 'green']);
  2. for (const x of set.values()) {
  3. console.log(x);
  4. }
  5. // Output:
  6. // 'red'
  7. // 'green'

The default way of iterating over Sets. Same as .values().

  1. const set = new Set(['red', 'green']);
  2. for (const x of set) {
  3. console.log(x);
  4. }
  5. // Output:
  6. // 'red'
  7. // 'green'
  • .forEach(callback: (value: T, value2: T, theSet: Set<T>) => void, thisArg?: any): void [ES6]

Loops over the elements of this Set and invokes the callback (first parameter) for each one. value and key are both set to the element, so that this method works similarly to Map.prototype.forEach. If thisArg is provided, this is set to it for each call. Otherwise, this is set to undefined.

  1. const set = new Set(['red', 'green']);
  2. set.forEach(x => console.log(x));
  3. // Output:
  4. // 'red'
  5. // 'green'

32.4.5. Symmetry with Map

The following two methods only exist so that the interface of Sets is similar to the interface of Maps. Each Set element is handled as if it were a Map entry whose key and value are the element.

  • Set.prototype.entries(): Iterable<[T,T]> [ES6]
  • Set.prototype.keys(): Iterable<T> [ES6]
    .entries() enables you to convert a Set to a Map:
  1. const set = new Set(['a', 'b', 'c']);
  2. const map = new Map(set.entries());
  3. assert.deepEqual(
  4. [...map.entries()],
  5. [['a','a'], ['b','b'], ['c','c']]);