Learning JavaScript Maps & Sets

Note: This post is work-in-progress learning-note and still in active development and updated regularly.

The ECMASript 2015 (ES6) defines seven data types: six are primitives (Boolean, Null, Undefined, Number, String & Symbol) and an Object. The object data type is fundamental to JavaScript (JS) language as it holds different data types: primitives, object and functions (as methods).

In previous posts, complex data structures such as objects (for storing keyed collections) and arrays (for storing index collections) were discussed. In ES6, Map & Set objects were introduced which are ordered by a key and are iterable in the order of insertion. In this learning-note post, we will discuss basic features of maps & sets, compare maps vs objects, array vs set. The WeakMap & WeakSet objects, which are very similar to the Maps & Sets will be discussed in a separate Understanding JavaScript WeakMaps & WeakSets post.

Maps

JS Maps were introduced in ES6 as a new data structure to map (as map of an object) value to value. They were introduced as an alternative to using Object literals for storing simple key/value pairs map and which can iterate its elements in insertion order.

Basic Syntax

The basic syntax of array.map() method, adopted from the MDN Documentation:

//initialize myMap with a new constructor
let myMap = new Map([iterable]); // empty map object
//example
let myMap = new Map([[1,2],[2,3]]); // myMap = {1=>2, 2=>3}

The constructor receives an array or iterable object as parameters whose elements are key-value pairs and returns a new Map object . In the example above the new Map constructor receive arrays with two [key, value] elements. The MDN defines the iterables as:

an Array or other iterable object whose elements are key-value pairs (arrays with two elements, e.g. [[ 1, 'one' ],[ 2, 'two' ]]). Each key-value pair is added to the new Map; null values are treated as undefined.

Maps Properties & Methods

The main array.map() methods and properties are as follows:

  • map.constructor : This is the default map function.
  • map.size : this property returns size or number of elements in the current map.
  • map.set(key, value) : assign the value of the key in map object.
  • map.get(key) : returns value associated with the key, and undefined (if key does not exist in the map).
  • map.has(key) : returns a Boolean value – true, it key exists, false, if key does not exist.
  • map.delete(key) : removes the value of the key and returns true. If the key does not exist returns false.
  • map.clear() : removes all key:value pairs from the map.
Maps are Object

The following example, adopted from the MDN Documentation,  shows some basic operation with map object:

// initialize myCar map with constructor
let myCar = new Map();

//assign var with string, object & function
let keyString = 'a string', // a string
    keyObj = {},            // an object
    keyFunc = function() {}; // a function

// setting the values with string, object & function
myCar.set(keyString, "value associated with 'a string'");
myCar.set(keyObj, 'value associated with keyObj');
myCar.set(keyFunc, 'value associated with keyFunc');

//check map length with size property
myCar.size; // 3

// getting the values from myCar Map object
myCar.get(keyString);    // "value associated with 'a string'"
myCar.get(keyObj);       // "value associated with keyObj"
myCar.get(keyFunc);      // "value associated with keyFunc"

myCar.get('a string');   // "value associated with 'a string'"
                         // because keyString === 'a string'
myCar.get({});           // undefined, because keyObj !== {}
myCar.get(function() {}) // undefined, because keyFunc !== function () {}

In the example above, myCar is initialized using its built-in new constructor (line 2). Because Map object allows keys of any data type, lets add values to myCar map object with a string (line 5), an object (line 6) and a function (line 7) using map.set(key, value) method (lines: 10-12).  Now, if we check the size of myCar map object with map.size property (line 15), it returns a value of 3 (line 15) confirming the three values have been added to it.

The individual element value of the myCar map object can be obtained using map.get(value) method (lines: 18-20). If value of key is not found, it returns an unidentified value (lines: 24-25).

Iterating Maps with for..of

Iteration of maps elements with for..of loop goes for each elements in the insertion order and returns an array of [key, value] pair.

The following example adapted from the MDN Map Documentation:

//initialize myCar set and add elements
let myCar = new Map();
myCar.set(0, 'Toyota');
myCar.set(1, 'Honda');
myCar.set(2, 'Ford');

//iterate key value
for (let [key, value] of myCar) {
  console.log(key + ' = ' + value);
}
//OUTPUT
0 = Toyota
1 = Honda
2 = Ford

In the example above, lets revisit myCar map object construct (line 2) used in our previous examples too. Now, lets add three key/value pairs to the myCar map object, one at a time (lines: 3-5). Using for..of loop on myCar map object (lines: 7-10), its key value pairs can be iterated in their order of insertion, as shown in its return output (lines: 12-14).

There are following three maps iteration methods:

  • map.keys() : returns a new iterator object for keys.
  • map.values() : returns a new iterator object for values.
  • map.entries() : returns a new iterator object for entries with [key, value] pair.

Lets apply these methods to myCar map object above (line 2) to iterate map keys, values and entries as shown below:

// iterate to print keys only
for (let key of myCar.keys()) {
  console.log(key);
}
//OUTPUT
0
1
2

//iterate values only
for (let value of myCar.values()) {
  console.log(value);
}
//OUTPUT
Toyota
Honda
Ford

//iterate map entries
for (let [key, value] of myCar.entries()) {
  console.log(key + ' = ' + value);
}
//OUTPUT
0 = Toyota
1 = Honda
3 = Ford

In the example above, for..of loop is used to myCar.keys() method to iterate only key values in their order of insertion (lines: 16-18) and print return in the console (lines: 20-22). Similarly, we can iterate only values using myCar.values() method (lines: 29-31) and key/value pairs (lines: 38-40) using myCar.entries() method (lines: 34-36).

Comparing Maps and Objects

The MDN Documentation lists following key features of <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map" target="_blank" rel="noopener">maps</a> and tips to decide when to use <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map" target="_blank" rel="noopener">maps</a> vs <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Working_with_Objects" target="_blank" rel="noopener">objects</a>:

  • Maps keys can be any value where as objects key can be only strings or symbols or any value that can be converted to a string.
  • Maps length can be easily determine using <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/size" target="_blank" rel="noopener">map.size</a> property where as object size has to be determined manually.
  • In maps, iteration of key/value order of element is in insertion order where it is not guaranteed in the objects.
  • An Object has a prototype, so there are default keys in the map. (this can be bypassed using map = Object.create(null)).

Some tips when to use maps :

  • When keys are unknown until run time and when all keys or all values are same type.
  • When there is a need to store primitive values as keys (because object converts numbers, Boolean or other primitive values into a string).
  • Use objects when there is logic that operates on individual elements.

Note: The WeakMaps objects, which are similar to Maps object with weak reference and will be discussed in a separate post.

Sets

The MDN Documentation describes:  Set objects are collections of values. You can iterate through the elements of a set in insertion order. A value in the Set may only occur once; it is unique in the Set‘s collection.

Basic Syntax

The basic syntax of Set object, adopted from the MDN Documentation:

//initialize mySet with a new Constructor
let mySet = new Set([iterable]); // emptySet object
//example
let mySet = new Set([1, 2, 3, 4, 4, 5]); 
//access mySet object
mySet; //OUTPUT => Set(5) [ 1, 2, 3, 4, 5 ]

The constructor accepts iterable object as parameters and returns a new Set object . A new Set object can be created from an array (as shown in the example above) but the Set object does not include  duplicate values (line 6). Please note, the array (line 4) contains one duplicate entry.

Sets Properties & Methods

The main array.Set() methods and properties are as follows:

  • Set.constructor : create new set using new Set(iterable) .. .This is the default Set function.
  • Set.size : number of values in the Set object.
  • Set.add(value) :  adds a new element with a given value to the Set object.
  • Set.delete(value) : removes the element associated with the value from the Set.
  • Set.has(value) :  returns a Boolean value – true, it value exists in the Set, false, if value does not exist.
  • Set.values() :   returns a new Iterator object that contains the values for each element in the Set object. It is similar to Set.keys() function.
  • Set.clear() : removes all the elements from the Set object.
Set Objects

In the following example, adopted from the MDN Documentation, we will examine some basic operations (methods & properties) described before:

// create a Set Object with constructor 
let carSet = new Set(); //empty Set object

//add element to Set object
carSet.add("Toyota");// => Set(1) {"Toyota"} 
carSet.add("Corolla", 2018);// => Set(2) {"Toyota", "Corolla"}
carSet.add(2018);
//invoke carSet object
carSet; // OUTPUT => Set(3) {"Toyota", "Corolla", 2018}

//iterate Set elements
for (let item of carSet) console.log(item);
//OUTPUT
Toyota
Corolla
2018

//other basic Set operations
carSet.has("Toyota"); // OUTPUT => true
carSet.delete('Corolla');// OUTPUT => true
carSet.size; //OUTPUT => 2

In the example above, we use Set.add(value) method to add elements to CarSet object (line 2). The Set.add(value)accepts only one element at a time (line 5) and more than one elements are ignored (line 6) to maintain distinct value without any duplicates.

The Sets objects has built-in method to iterate through the elements in insertion order. By using for..of loop to Set object items (line 12), carSet object elements can be printed in their order of insertion (lines 14-16).

Examples of a few basic Set object operations, like to delete an element (line 20) with delete(value), checking size with Set.size (line 21) and checking if an element is in the Set (line 19) with Set.has(value) are also shown above (lines 19-21).

Additional examples of basic Set operation with some in-line explanation, adopted from the MDN Documentation, are shown below:

//initialize Set Objects with Constructor
let carSet = new Set(); // empty Set Object

//add elements with add
carSet.add("Toyota");//OUTPUT => Set(1) {"Toyota"}
carSet.add("corolla");//OUTPUT => Set(2) {"Toyota", "corolla"}
carSet.add(2018);//OUTPUT => Set(3) {"Toyota", "corolla", 2018}
//duplicate element is ignored
carSet.add(2018);//OUTPUT => Set(3){"Toyota", "corolla", 2018} 

let truck = {make: "Toyota", model: "Tundra"}; // define an object
// add truck Objects
carSet.add(truck); //OUTPUT => Set(4) {"Toyota", "corolla", 2018, {…}}
// add truck object as key:value
carSet.add({make: "Toyota", model: "Tundra"});
//OUTPUT
Set(5) {"Toyota", "corolla", 2018, {…}, {…}}

//test with .has method
carSet.has("corolla"); //OUTPUT => true
carSet.has("Ford"); //OUTPUT => false (has not been added)
carSet.has(2018); //OUTPUT => true
carSet.has(truck); //OUTPUT => true

//size property
carSet.size; //OUTPUT => 5

//delete value
carSet.delete("corolla"); // true, "corolla" removed from Set
//confirm delete
carSet.has("corolla"); //OUTPUT => false , "corolla" has been removed

carSet.size; //OUTPUT => 4 , one deleted
console.log(carSet);
//OUTPUT
Set(4) {"Toyota", 2018, {make:"Toyota", model:"Tundra"}, object{…}}
Iterating Sets

Continuing our CarSet example (from above, adopted from the MDN Documentation) lets examine below some iteration operations on CarSet object:

// starting carSet object from line 36
for (let item of carSet) console.log(item);
//OUTPUT (in the order)
Toyota
2018
{make: "Toyota", model: "Tundra"}
{make: "Toyota", model: "Tundra"}

for (let item of carSet.keys()) console.log(item);
//OUTPUT (in the order)
Toyota, 2018, {make:"Toyota", model:"Tundra"}, {make:"Toyota", model:"Tundra"}

for (let item of carSet.values()) console.log(item);
//OUTPUT (in the order)
Toyota, 2018, {make:"Toyota", model:"Tundra"}, {make:"Toyota", model:"Tundra"}

for (let [key, value] of carSet.entries()) console.log(key);
//OUTPUT (in the order)
Toyota, 2018, {make:"Toyota", model:"Tundra"}, {make:"Toyota", model:"Tundra"}

// convert Set object to an Array object, with Array.from
var myCar = Array.from(carSet);
// access myCar
myCar; //OUTPUT => (4) ["Toyota", 2018, {…}, {…}]

In  the example above, continuing from the carSet (line 36) we will make use for..of to iterate items in carSet. When for..of iteration loop is used on Carset (line 38), carSet.keys() (line 45), carSet.values() (line 49), and carSet.entries() (line 53) it returns items of carSet in order (lines 40-43, 47, 51 & 53).

Using Array.from method on carSet (line 58) the carSet items were converted into an array and returns assigned to a new variable myCar (line 60). The Array.from method can be used in the other direction too.

Converting Sets & Array

A Set object can be used to convert to an array using <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/from" target="_blank" rel="noopener">Array.from</a> or spread operator. Conversely, Set constructor also accepts an array to convert in other direction, any duplicate elements in array are deleted.  The following example adopted from MDN Documentation:

// convert set to array
let hrCars = new Set(["Toyota", "Ford", "Honda", "GM" ]);

//convert with Array.from() 
let hrCarsArray = Array.from(hrCars);
//invoke hrCarsArray
console.log(hrCarsArray);// =>(4) ["Toyota", "Ford", "Honda", "GM"]
//convert with spread operator
[...hrCars]; //OUTPUT => (4) ["Toyota", "Ford", "Honda", "GM"]

In the example above, using spread syntax  on hrCars (line 9) returned the exact same array (line 9) as that of hrCarsArray (line 7) return from Array.from method (line 5).

Basic Set Operations

The ES6 <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set" target="_blank" rel="noopener">Set</a> object has no methods for computing Union, Intersection or Difference. But it is possible to perform these basic Set operations with some simple functions.

Union: It creates a new Set that contains elements of both sets (eg. Set a, Set b) without any duplicates.

Intersection: It creates a new Set that contains elemets that are present in both sets.

Difference: It creates a new Set containing elements that are present in onse set but in the other set.

Lets explain these Set operations using following example, adopted from the MDN Set Reference and some explanation borrowed from Understanding Sets With JavaScript.

// initialize two sets
let hrCars = new Set(["Toyota", "Ford", "Honda", "GM" ]);
let adCars = new Set (["BMW", "Toyota", "Honda", "Lexus"]);

//Union of two sets
let ofVehicles = new Set([...hrCars, ...adCars]);
console.log(ofVehicles);
//OUTPUT
Set(6) {"Toyota", "Ford", "Honda", "GM", "BMW", …}

//intersection
let comVehicles = new Set([...hrCars].filter(x => adCars.has(x)));
console.log(comVehicles);
//OUTPUT
Set(2) {"Toyota", "Honda"}

//difference
let uniqVehicles = new Set([...hrCars].filter(x => !adCars.has(x)));
console.log(uniqVehicles);
//OUTPUT
Set(2) {"Ford", "GM"}

//iterate set entries with forEach (ofVehicles)
ofVehicles.forEach(function(value) {
  console.log(value);
});
//OUTPUT
Toyota
Ford
Honda
GM 
BMW
Lexus

In the example above, two Set objects: hrCars (human resource) and adCars (admin) are initialized (lines: 2-3) where some vehicles (Toyota, Honda) are common in both sets.

The Union of two Sets named ofVehicles (office vehicle) can be created using a  new Construct (line 6) with ... spread operator. The new Set Constructor removes any duplicates and returns unique vehicles (line 9) that are present in either or both the departments.

The intersection of two Sets named comVehicles (common vehicles) is set containing vehicles in the both departments (line 12).  This is achieved by searching in hrCars set and checking if each vehicle is present in adCars set with Set.has(value) method. Now with has method vehicles in adCars set can be filtered out with the vehicles that are also present in adCars set and assign to comVehicles (common vehicle) set (line 12). The comVehicle set returns two vehicles (line 15) that are present in both department.

The difference of two Sets named uniqVehicles (unique vehicles) is group of vehicles that are present one set but not in the other set. This can be achieved with an approach similar to intersection (described before) but filtering out any duplicate vehicles from other set and deleting (eg. filter(x => !adCars.has(x)  ) as shown in line 18. The uniqVehicles set returns two unique vehicles (line 21).

Using for..each loop list of vehicles in ofVehicles set can be printed out in browser console as shown in lines (23-33).

Relation with String

Using Sets object constructor a string variable can be converted into an array. Using below an example adopted from the MDN Documention:

//initialize string value
let myCar = "Toyota";

//initialize carSet
let carSet = new Set(myCar);
//invoke carSet; 
carSet;//OUTPUT => Set(5) {"T", "o", "y", "t", "a"}

//check size property
carSet.size; //OUTPUT => 5

In the example above, myCar was initialized with "Toyota" string (has five characters). Using the new Set constructor and passing a string variable as its parameter (line 5), a string variable can be converted to a Set as shown above (line 7). The size of new Set is equivalent to the size of the string (line 10).

Comparing Sets and Arrays

There is lot of similarities between JS Sets and Arrays, both store multiple values in a single variable. The MDN Documentation lists some key features of Sets :

  • To check whether an element exist in an collection, indexOf() is used in arrays (which is slow) where as in Set object, it is checked using much faster Set.has(value) method.
  • In Set object, elements can be deleted from an collection by their values, while in an array element value is deleted based on an element’s index.
  • The value of NaN can be found in Set object using Set.has(NaN) method but NaN can’t be found using indexOf() in an array.
  • Unlike in an array, only unique values can be stored in Set objects (duplicate values are ignored).

Note: The WeakSets objects, which are similar to Sets object with weak reference and will be discussed in a separate post.

Wrapping Up

In this post, we looked at ES6 Maps and Sets Objects, their basic syntax, adding elements, use of built-in methods to manipulate Maps & Sets objects, Iteration with for..of loop, and basic Sets operation. A brief comparison of Maps vs Objects and Sets vs Array is discussed. The WeakMaps and WeakSets will be discussed separately in an other post.

NEXT: Understanding JavaScript WeakMaps & WeakSets

Useful Resources & Links

While preparing this post, I have referred the following references extensively. Visit original link for additional information.