JavaScript for impatient programmers (beta)
Please support this book: buy it or donate
(Ad, please don’t block.)

32. Sets (Set)



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

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:

const emptySet = new Set();
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:

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

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

const set = new Set()
.add('red')
.add('green')
.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.

const set = new Set();

set.add('red');
assert.equal(set.has('red'), true);

assert.equal(set.delete('red'), true); // there was a deletion
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.

const set = new Set()
  .add('foo')
  .add('bar')
;

assert.equal(set.size, 2)
set.clear();
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:

const set = new Set(['red', 'green', 'blue']);
for (const x of set) {
  console.log(x);
}
// Output:
// 'red'
// 'green'
// '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:

const set = new Set(['red', 'green', 'blue']);
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:

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

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

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

32.2. Comparing Set elements

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

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

Adding an element a second time has no effect:

> const set = new Set();

> set.add('foo');
> set.size
1

> set.add('foo');
> set.size
1

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

> const set = new Set();

> set.add({});
> set.size
1

> set.add({});
> set.size
2

32.3. Missing Set operations

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

32.3.1. Mapping

const set = new Set([1, 2, 3]);
const mappedSet = new Set([...set].map(x => x * 2));

// Convert mappedSet to an Array to check what’s inside it
assert.deepEqual([...mappedSet], [2, 4, 6]);

32.3.2. Filtering

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

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.

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

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.

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

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

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

assert.deepEqual([...difference], [1]);

32.4. Quick reference: Set<T>

32.4.1. Constructor

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

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

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

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.

.entries() enables you to convert a Set to a Map:

const set = new Set(['a', 'b', 'c']);
const map = new Map(set.entries());
assert.deepEqual(
  [...map.entries()],
  [['a','a'], ['b','b'], ['c','c']]);

  Quiz

See quiz app.