Apps Script V8 implements a couple of useful new ES6 structures – Maps and Sets. Here’s what they are and how to use them

Maps and Sets: What are they?

These are a collection of like or unlike items. Unlike an array, they are aware of what else is in the map or set (so you can avoid duplicates), and unlike an object, you can use anything as the key – including the item value itself.

The difference between Maps and Sets

Maps

A Map is a collection of key/value pairs, whereas a Set is a collection of items. A Set actually has both values and keys too, but they are the same as each other – in other words, the value of the item is used as its key in a Set, whereas the key and the value can be different in a Map.

Sets

Let’s start with some examples of creating and populating Sets. We’ll use the same data as in previous articles on V8.

const setPeople = [{ 
  firstName:'John', 
  middleName: 'Eustace', 
  lastName:'Smith',
},{
  firstName:'Jane', 
  lastName:'Doe' 
}];

creating

An empty set is created like this

  const family = new Set()
  // {}
  console.log(family)

adding

the add method takes the item you want to add as an argument

  const [john, jane] = setPeople
  family.add(john)

has

the has method checks for the existence of an item

  // true
  console.log(family.has(john))
  // false
  console.log(family.has(jane))

keys and value

The key and the value are the same things in a Set, so values and keys return the same result (unlike Map). That’s why there is no get method for a Set. It’s not required, because you already have to know the value you are looking for as it is the same as key

  // keys and values of sets are the same thing.
  // { firstName: 'John', middleName: 'Eustace', lastName: 'Smith' }
  for (let value of family.values()) {
    console.log(value)
  }
  // { firstName: 'John', middleName: 'Eustace', lastName: 'Smith' }
  for (let key of family.keys()) {
    console.log(key)
  }

Adding when value already exists, and size

Re-adding the same value has no effect, and causes no error. You can use the size property to examine how many items are in the Set

  // john already exists
  family.add(john)
  // 1
  console.log(family.size)
  family.add (jane)
  // 2
  console.log(family.size)
  // true
  console.log(family.has(jane))

Deleting

the delete method

  family.delete(john)
  // 1
  console.log(family.size)
  // { firstName: 'Jane', lastName: 'Doe' }

Iterating forEach

Aside from using the keys and values methods, the simplest iteration method is forEach

// { firstName: 'Jane', lastName: 'Doe' } 
  family.forEach(f=>console.log(f))

Converting to an array

A common operation is to turn a Set into an Array

  family.add (john)
  const familyArray = Array.from(family)
  // [ { firstName: 'Jane', lastName: 'Doe' },
  // { firstName: 'John', middleName: 'Eustace', lastName: 'Smith' } ]
  console.log(familyArray)

Values are by reference

An object stored in a set is not a deep clone of the object, it’s just a reference to it – so changing the original object, also changes the contents of the set (just as would happen with an item in an Array).

  family.delete(jane)
  // what happens if we change john 
  john.firstName = 'jon'
  // { firstName: 'jon', middleName: 'Eustace', lastName: 'Smith' }
  for (let value of family.values()) {
    console.log(value)
  }

Removing dups

Dups can’t exist in Sets, so they are a great way of removing them. Here are two ways of removing dups from an array. Removing dups from a Set is just a matter of adding the Array to a set.

  // removing dups
  const list = [1,2,2,3,4,5,6,6,7,7]
  // traditionally
  // [ 1, 2, 3, 4, 5, 6, 7 ]
  console.log(list.filter((f,i,a)=>a.indexOf(f)===i))
  // with sets
  // [ 1, 2, 3, 4, 5, 6, 7 ]
  console.log(Array.from(new Set(list)))

Anything can be a key/value

  // anything in a set
  const panda = '🐼'
  const frog = '🐸' 
  // 🐼 🐸
  console.log(panda,frog)
  const zoo =  new Set([panda,frog])
  // 🐼 🐸
  for (let value of zoo.values()) {
    console.log(value)
  }
  zoo.add(panda)
  //[ '🐼', '🐸' ]
  console.log(Array.from(zoo))
  zoo.delete(frog)
  // [ '🐼' ]
  console.log(Array.from(zoo))
  // true/false
  console.log(zoo.has(panda), zoo.has(frog))

Maps

Maps and Sets are very similar, except

  • Maps are key/value pairs – where the key can be different from the value
  • Sets are key/value pairs – but the key and value are the same as each other

This section will be very similar to the section on Sets, as the structures are largely the same

creating

An empty Map is created like this

  const family = new Map()
  // {}
  console.log(family)
  // add someone

adding

the set method takes a key, plus the item you want to add as  arguments

  // add someone
  const [john, jane] = setPeople
  family.set('john', john)

has

the has method checks for existence of an item

  // true
  console.log(family.has('john'))
  // false
  console.log(family.has('jane'))

keys and value

The key and the value can be different in Map items (unlike Set items)

  // keys and values of maps are different
  // { firstName: 'John', middleName: 'Eustace', lastName: 'Smith' }
  for (let value of family.values()) {
    console.log(value)
  }
  // 'john'
  for (let key of family.keys()) {
    console.log(key)
  }

Adding when value already exists, and size

Re-adding the same value has no effect, and causes no error. You can use the size property to examine how many items are in the Map

  // overwrites john
  family.set('john',john)
  // 1
  console.log(family.size)
  family.set ('jane', jane)
  // 2
  console.log(family.size)
  // true
  console.log(family.has('jane'))

Deleting

the delete method

  family.delete('john')
  // 1
  console.log(family.size)

Iterating forEach

Aside from using the keys and values methods, the simplest iteration method is forEach

  // { firstName: 'Jane', lastName: 'Doe' }
  family.forEach(f=>console.log(f))

Converting to an array

A common operation is to turn a Map into an Array

  family.set ('john', john)
  const familyArray = Array.from(family)
  // [ { firstName: 'Jane', lastName: 'Doe' },
  // { firstName: 'John', middleName: 'Eustace', lastName: 'Smith' } ]
  console.log(familyArray)

Values are by reference

An object stored in a Map is not a deep clone of the object, it’s just a reference to it – so changing the original object, also changes the contents of the Map (just as would happen with an item in an Array).

  family.delete(jane)
  // what happens if we change john 
  john.firstName = 'jon'
  // { firstName: 'jon', middleName: 'Eustace', lastName: 'Smith' }
  for (let value of family.values()) {
    console.log(value)
  }

Removing dups

Dups (items with the same key) can’t exist in Maps, so they are a great way to remove them. Here are two ways of removing dups from an array. Removing dups from a Map is just a matter of adding the Array to a Map – but to add, you must first create a key-value pair array for each item of the Array, as in the example below.

  // removing dups
  const list = [1,2,2,3,4,5,6,6,7,7]
  // traditionally
  // [ 1, 2, 3, 4, 5, 6, 7 ]
  console.log(list.filter((f,i,a)=>a.indexOf(f)===i))

  // convert to key/value pair, load to map, and extract the dedeupped values
  console.log([...new Map(list.map(f=>[f,f])).values()])

Anything can be a key

// anything in a map
  const panda = {pic:'🐼', person: john}
  const frog = {pic:'🐸', person: jane } 
  // { pic: '🐼',person: { firstName: 'jon', middleName: 'Eustace', lastName: 'Smith' } } 
  // { pic: '🐸', person: { firstName: 'Jane', lastName: 'Doe' } }
  console.log(panda,frog)
  // convert array to key value pair
  const zoo =  new Map([[panda.pic,panda.person],[frog.pic, frog.person]])
  // 🐼 🐸
  for (let key of zoo.keys()) {
    console.log(key)
  }
  // { firstName: 'jon', middleName: 'Eustace', lastName: 'Smith' }
  // { firstName: 'Jane', lastName: 'Doe' }
  for (let value of zoo.values()) {
    console.log(value)
  }
  zoo.set(panda.pic, panda.person)
  //[ '🐼', '🐸' ]
  console.log(Array.from(zoo.keys()))
  zoo.delete(frog.pic)
  // [ '🐼' ]
  console.log(Array.from(zoo.keys()))
  // true/false
  console.log(zoo.has(panda.pic), zoo.has(frog.pic))

get

There is no get with Sets, but it is necessary to find a value by its key with Maps. It’s as you’d expect.

  // { firstName: 'jon', middleName: 'Eustace', lastName: 'Smith' }
  console.log(zoo.get(panda.pic))

Iterating with entries

Another iteration approach is by extracting the key and value from the entries method. This also works for Sets but is kind of pointless since keys an values are the same.

  // 🐼 { firstName: 'jon', middleName: 'Eustace', lastName: 'Smith' }
  for (let [key, value] of zoo ) {
    console.log(key, value)
  }

Summary

Sets and Maps can often be a cleaner way of storing data than using Objects or Arrays, even though at first glance they may seem a little redundant. Give them a try. You’ll like them.

Weakmaps and Symbols are related to this topic, but I’ll cover them in a separate article.