An Immutable Set provides powerful Set operations such as intersect, union and subtract. This tutorial shows you how to use them, and when to use them instead of merging Lists.

When to use Sets and the Union, Intersect and Subtract methods

The unique property of a Set is that its values are always unique. Immutable’s Set methods enforce this uniqueness, and will automatically prevent duplicates from being added to a Set, without throwing an exception.

A Set really comes into own when using the three Set-specific methods, union, intersect and subtract.

  • Set.union() combines the values of two Sets together, ensuring no duplicates exist. Union returns the values of Set B combined with those of Set A with duplicates removed. It differs from List.merge(), which overwrites the values of List A with the values of List B that exist at the same index. In contrast, Set.union() combines two Sets of values.
  • Set.intersect() takes two Sets, A and B, and returns a Set that contains only those values that exist in A and B.
  • Set.subtract() takes two Sets, A and B, and returns a Set that contains only those values that exist in A, but do not exist in B.

This ability to manipulate data and ensure uniqueness makes Sets extremely powerful, something we’ll look at in more detail in a forthcoming tutorial on Immutable recipes (or ‘how to actually use Immutable.js’!). In the meantime, let’s look at how you actually use them.

How to operate on an Immutable Set

Working with Primitive Values (numbers, strings, etc.)

When performing Set manipulation operations (i.e. subtract, intersect or union), you need to understand how JavaScript deals with equality. With JavaScript primitive values (booleans, numbers, strings, null, and undefined), equality is as you’d expect: 2 always equals 2, for example, while 'string' === string'.

Working with JavaScript Objects

With JavaScript objects, however, it’s very different. Specifically, an object is only ever equal to itself. Creating two separate objects with identical keys and values will not make them equal.

This is because objects are compared by reference (i.e. their location in memory), whereas primitives are compared by value. If two numbers have the same value, they are deemed to be equal. Two object with the same value, though, will always reside in two different memory locations, and so cannot be equal.

An object is only ever equal to itself. Creating two separate objects with identical keys and values will not make them equal.

For example, these two objects are not equal:


const obj1 = { key: 'value' };
const obj2 = { key: 'value' }

// The values assigned obj1 and obj2 reside in two different memory locations. 
// They are therefore not equal:
obj1 === obj2;

Why is this important? Because a Set must have unique values, and in order to check for duplicates, it needs to know (and so do you!) what makes one value ‘equal to’ another. In the case of primitives, it’s the value itself, but in the case of objects, equality is down to memory location.

This can cause confusion when using Set’s methods (such as subtract, as intersect and as union) on a Set of unassigned JavaScript objects, as there is no way to reference the individual objects in the Set, and so no way for the subtract() function to operate on them.

For example, the following will not work:


// NOTE: does NOT work

// Create a Set of objects, none of which are assigned to a variable
const avengersSet = Immutable.Set([{ blackWidow: 'Natasha Romanov' }, { captainAmerica: 'Steve Rogers' }]);

// Try to remove blackWidow using the key/value pair, and it won't work
avengersSet.subtract([{ blackWidow: 'Natasha Romanov' }]);

Each object passed to Set in this example exists in a specific memory location, but because it’s not been assigned to a variable, there is no way to reference it. Simply using the key/value pair as an argument in the subtract() method is not enough, as this just creates a separate object in a different memory location, and so has a different identity.

For two objects to be treated as equal, you must create one object, assign two different variables to it, and then compare the variables for equality. This way, there is only one object in memory, and each variable assigned to it is pointing to the single location in memory; thus, the two variables are equal, as they both reference the same object.

For example, the following variable assignment makes obj2 equal to obj1:



const obj1 = { key: 'value' };
const obj2 = obj1;
obj1 === obj2;

Applying this to Set’s functions, in order to safely use subtract, intersect or union on a Set of objects, we must create a Set of assigned objects: that is, a Set of variables, each of which is assigned to an object, rather than a Set of objects directly.

In other words, do this:


// Create variables and assign them to objects
const blackWidow = {
  blackWidow: 'Natasha Romanov'
};

const captainAmerica = {
  captainAmerica: 'Steve Rogers'
}

// Create a Set of variables, each of which is assigned to an object
const avengersSet = Immutable.Set([captainAmerica, blackWidow]);

// Output:
avengersSet.subtract([blackWidow]);


To use a Set of objects, assign each object to a variable and create a Set of variables instead.

Working with Immutable objects (Map, List, Set, etc.)

Because of the difficulty of working with JavaScript objects, Immutable has its own equality method for its objects (Map, List, Set, etc.) in the form of the Immutable.is() method, which will treat two Immutable objects as equal if their values are equal.

That is, the following two Immutable Maps are treated as equal, even though they’re two different objects (i.e. they reside in different memory locations):


// Create variables and assign them to objects
const obj1 = Immutable.Map({ key: 'value' });
const obj2 = Immutable.Map({ key: 'value' });

Immutable.is(obj1, obj2);

This works with any Immutable object, not just Maps. The Immutable.is() method is used internally by Set’s methods to determine equality when acting on Immutable objects, which makes working with Sets of such objects much easier to work with than Sets of plain JavaScript objects.

So with all that in mind, let’s see some examples of how Immutable’s Set operations actually work.

(Note: this has been quite a deep dive into the mechanics of JavaScript assignment. If you’re looking for more punishment, read Dr. Axel Rauschmayer’s excellent book ‘Speaking JavaScript’ on JavaScript Assignment. It’s applicable to both ES5 and ES6).

Subtract

Subtract works either with an array of Immutable Iterables (e.g. Map, List, Set, etc), or a JavaScript array:

  • const subtractedSet = originalSet.subtract([aList])
  • const subtractedSet = originalSet.subtract([anArray])

…a string from a Set of strings

Remember, the argument in Set.subtract() must always be an array, even if there’s only one value you want to subtract.


// Subtract an string from a Set of strings
const avengersSet = Immutable.Set(['ironMan', 'captainAmerica', 'blackWidow']);

// Output:
avengersSet.subtract(['blackWidow']);


…an array of strings from a Set of strings


// Subtract an string from a Set of strings
const avengersSet = Immutable.Set(['ironMan', 'captainAmerica', 'blackWidow']);

// Output:
avengersSet.subtract(['blackWidow', 'captainAmerica']);


…an object from a Set of objects


// Subtract an object from a Set of objects

// Subtract blackWidow from avengersSet
const blackWidow = {
  blackWidow: 'Natasha Romanov'
};

const avengersSet = Immutable.Set([{
  ironMan: 'Tony Stark'
}, {
  captainAmerica: 'Steve Rogers'
}, blackWidow]);

// Output:
avengersSet.subtract([blackWidow]);

Note that the variable named blackWidow is assigned to the object { blackWidow: 'Natasha Romanova' } first, and then added as part of avengersSet. We do this so that blackWidow can be used in the subtract() method to identify the object we want to subtract.

It’s worth repeating that you cannot use a key/value pair to identify an object to subtract from a Set of objects: you must first assign a variable to an object and use that variable name in the subtract() method.


…an array of objects from a Set of objects


// Subtract an object from a Set of objects

// Subtract blackWidow and captainAmerica from avengersSet
const blackWidow = {
  blackWidow: 'Natasha Romanov'
};

const captainAmerica = {
  captainAmerica: 'Steve Rogers'
}

const avengersSet = Immutable.Set([{
  ironMan: 'Tony Stark'
}, captainAmerica, blackWidow]);

// Output:
avengersSet.subtract([blackWidow, captainAmerica]);


…a Map from a Set of Maps


// Subtract a Map from a Set of Maps

// First, create our Maps
const ironMan = Immutable.fromJS([{
  ironMan: 'Tony Stark'
}, {
  captainAmerica: 'Steve Rogers'
}, {
  blackWidow: 'Natasha Romanov'
}]);

// Create a Set of Maps
const avengersSet = Immutable.Set(ironMan);

// Now subtract blackWidow (sorry Natasha)
avengersSet.subtract([Immutable.fromJS({
  blackWidow: 'Natasha Romanov'
})]);


…a List from a Set of Lists


// Subtract a List from a Set of Lists

// First, create our Lists
const avengers = Immutable.fromJS([
  ['ironMan', 'Tony Stark'], 
  ['captainAmerica', 'Steve Rogers'],
  ['blackWidow', 'Natasha Romanov']]);

// Create a Set of Lists
const avengersSet = Immutable.Set(avengers);

// Now subtract ironMan (so long, Tony)
const ironMan = Immutable.List(['ironMan', 'Tony Stark'])

// Remember, subtract requires its arguments to be placed in an array
avengersSet.subtract([ironMan]);


…one Set from another


// Subtract a Set from another Set

// First, create our Sets
const ironMan = Immutable.Set(['ironMan', 'Tony Stark']);

const captainAmerica = Immutable.Set(['captainAmerica', 'Steve Rogers']);

const blackWidow = Immutable.Set(['blackWidow', 'Natasha Romanov']);

// Create a Set of Sets
const avengersSet = Immutable.Set([ironMan, captainAmerica, blackWidow]);

// Now subtract captainAmerica (bye Steve)
avengersSet.subtract([Immutable.Set(['captainAmerica', 'Steve Rogers'])]);


Union

Set.Union() merges two Sets together, ensuring there are no duplicates in the resulting Set. It works either with an array of Immutable Iterables (e.g. Map, List, Set, etc), or a JavaScript array:

  • const subtractedSet = originalSet.union([aList])
  • const subtractedSet = originalSet.union([anArray])

Like Set.subtract(), all arguments for Set.union() must be arrays.

Create a Union of…

…two sets of strings


// Create a union of two Sets of strings
const avengersCast = Immutable.Set(['ironMan', 'captainAmerica', 'blackWidow']);
const civilWarCast = Immutable.Set(['ironMan', 'antMan', 'spiderMan']);

// Output:
avengersCast.union(civilWarCast);


…a Set of strings and an array of strings


// Create a union of two Sets of strings
const avengersCast = Immutable.Set(['ironMan', 'captainAmerica', 'blackWidow']);

// Output:
avengersCast.union(['ironMan', 'antMan', 'spiderMan']);


…a Set of objects and an object


// Add an object to a set of objects with union

// Add blackWidow to avengersSet
const blackWidow = {
  blackWidow: 'Natasha Romanov'
};

const avengersSet = Immutable.Set([{
  ironMan: 'Tony Stark'
}, {
  captainAmerica: 'Steve Rogers'
}, blackWidow]);

// Output:
avengersSet.union([blackWidow]);


…a Set of Maps and a Map


// Add a Map to a Set of Maps

// First, create our Maps
const ironMan = Immutable.fromJS([{
  ironMan: 'Tony Stark'
},{
  captainAmerica: 'Steve Rogers'
}]);

// Create a Set of Maps
const avengersSet = Immutable.Set(ironMan);

// Now add blackWidow (hello Natasha)
avengersSet.union([Immutable.Map({
  blackWidow: 'Natasha Romanov'
})]);


…a Set of Maps and an object


// Add a Map to a Set of Maps

// First, create our Maps
const ironMan = Immutable.fromJS([{
  ironMan: 'Tony Stark'
},{
  captainAmerica: 'Steve Rogers'
}]);

// Create a Set of Maps
const avengersSet = Immutable.Set(ironMan);

// Now add blackWidow (hello Natasha)
avengersSet.union([{
  blackWidow: 'Natasha Romanov'
}]);

…a Set of Lists and a List


// Add a List to a Set of Lists

// First, create our Lists
const ironMan = Immutable.List(['ironMan', 'Tony Stark']);

const captainAmerica = Immutable.List(['captainAmerica', 'Steve Rogers']);

const blackWidow = Immutable.List(['blackWidow', 'Natasha Romanov']);

// Create a Set of Lists
const avengersSet = Immutable.Set([captainAmerica, blackWidow]);

// Now add ironMan (Hi, Tony)
avengersSet.union([ironMan]);


…two Sets


// Add a Set to another Set

// First, create our Sets
const ironMan = Immutable.Set(['ironMan', 'Tony Stark']);

const captainAmerica = Immutable.Set(['captainAmerica', 'Steve Rogers']);

const blackWidow = Immutable.Set(['blackWidow', 'Natasha Romanov']);

// Create a union of two Sets
const avengersSet = Immutable.Set([ironMan, blackWidow]);

// Now add captainAmerica
avengersSet.union([captainAmerica]);


Intersect

The intersection of two Sets is a Set containing only those values that exist in both Sets. If a value exists in Set A, but not Set B (or vice versa), then it is not included in the returned intersected Set.

Like Subtract and Union, Intersect works either with an array of Immutable Iterables (e.g. Map, List, Set, etc), or a JavaScript array:

  • const intersectedSet = originalSet.intersect([aList])
  • const intersectedSet = originalSet.intersect([anArray])

…two Sets of objects


// Intersect two Sets of objects

// intersect avengersCast with civilWar cast
const blackWidow = {
  blackWidow: 'Natasha Romanov'
};

const ironMan = {
  ironMan: 'Tony Stark'
};

const captainAmerica = {
   captainAmerica: 'Steve Rogers'
};

const theHulk = {
   theHulk: 'Bruce Banner'
};

const antMan = {
   antMan: 'Scott Lang'
};

const spiderMan = {
   spiderMan: 'Peter Parker'
};

const avengersCast = Immutable.Set([ironMan, captainAmerica, blackWidow, theHulk]);
const civilWarCast = Immutable.Set([ironMan, captainAmerica, blackWidow, antMan, spiderMan]);

// Output: who was in Avengers and Civil War?
avengersCast.intersect(civilWarCast);

Note: this is a horrible way of achieving this! It’s much easier to use fromJS() and convert everything to Immutable first. Take a look at the intersection of two Maps for an easier example.


…two Sets of arrays


// Intersect two Sets of arrays

const ironCapArray = ['ironMan', 'captainAmerica'];
const blackHulkArray = ['blackWidow', 'theHulk'];
const spiderAntArray = ['spiderMan', 'antMan'];

const avengersCast = Immutable.Set([ironCapArray, blackHulkArray]);
const civilWarCast = Immutable.Set([ironCapArray, spiderAntArray]);

// Output:
avengersCast.intersect(civilWarCast);


…two Sets of Maps


// Intersect two Sets of Maps

// First, use fromJS() to create a List of Maps
const avengersCast = Immutable.fromJS([{
  blackWidow: 'Natasha Romanov'
}, {
  ironMan: 'Tony Stark'
}, {
  captainAmerica: 'Steve Rogers'
}, {
  theHulk: 'Bruce Banner'
}]);

const civilWarCast = Immutable.fromJS([{
  blackWidow: 'Natasha Romanov'
}, {
  ironMan: 'Tony Stark'
}, {
  captainAmerica: 'Steve Rogers'
}, {
  antMan: 'Scott Lang'
}, {
  spiderMan: 'Peter Parker'
}]);

// Now create two Sets of Maps
const avengersSet = Immutable.Set(avengersCast);
const civilWarSet = Immutable.Set(civilWarCast);

// Output: who was in Avengers and Civil War?
avengersSet.intersect(civilWarCast);


…two Sets of Lists


// Intersect two Sets of arrays

const ironCapList = Immutable.List(['ironMan', 'captainAmerica']);
const blackHulkList = Immutable.List(['blackWidow', 'theHulk']);
const spiderAntList = Immutable.List(['spiderMan', 'antMan']);

const avengersCast = Immutable.Set([ironCapList, blackHulkList]);
const civilWarCast = Immutable.Set([ironCapList, spiderAntList]);

// Output:
avengersCast.intersect(civilWarCast);


…two Sets of Sets


// Intersect two Sets of arrays

const ironCapSet = Immutable.Set(['ironMan', 'captainAmerica']);
const blackHulkSet = Immutable.Set(['blackWidow', 'theHulk']);
const spiderAntSet = Immutable.Set(['spiderMan', 'antMan']);

const avengersCast = Immutable.Set([ironCapSet, blackHulkSet]);
const civilWarCast = Immutable.Set([ironCapSet, spiderAntSet]);

// Output:
avengersCast.intersect(civilWarCast);

Summary

Well, that was certainly more involved than I expected! But now you know how to use the Immutable Set operations, you’ll be able to do funky things like:

  • remove duplicates from a List
  • find all users in one List who are not in anther (e.g. all the users who have not paid you!)
  • find all users in two Lists (e.g. all the users applying for a loan with a bad credit history)
  • remove users from a List (e.g. remove those users with a bad credit history who have applied for a loan!)
  • combine two Lists without adding duplicates

And much, much more!

But all that’s for another tutorial. This one’s already broken my brain!

There are no comments.

Leave a Reply

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>