Tuples and Maps in Scala

Tuples and Maps in Scala

This post is part 6 of the Functional Programming in Scala series. You can view all the posts in the series here.

TLDR;

A Tuple is basically grouping data of different types.

  • To create a tuple we can do val tuple = (43, "James Willett")
  • To access the members of a tuple: tuple._1
  • To copy a tuple and change some values: tuple.copy(_1 = 0)
  • To pretty print them: tuple.toString
  • And to swap the values: tuple.swap

Maps are a useful data structure to make associations between keys and values.

  • We can create a map like so: val myMap = Map("John" -> 234, "Mary" -> 7879)
  • Check a value exists in the map - myMap.contains("John")
  • Access values in the map - myMap("Mary")
  • Add a new pairing to the map by creating a new one: val anotherMap = myMap + ("Bob", 123)

The filterKeys and mapValues functionals are very useful for accessing data in maps.

map, flatmap, and filter are also used on the pairings of maps.

We can transform maps to and from other collections, with methods like toList and toMap and groupBy.


Introducing Tuples in Scala

Tuples in Scala are similar to lists, they are finite orders of data. A tuple can be used to group together data of different types, for example Strings and Integers:

val aTuple = new Tuple2(2, "hello, Scala")

The type of the above tuple is Tuple2[Int, String]. This is known to Scala as simply (Int, String). The parentheses is syntactic sugar for Tuple2 type parameterised with Int and String.

In Scala you can create tuples either with or without the new keyword. Specifying the new keyword isn’t necessary as each Tuple companion object has an appropriate apply method. So you can create a tuple like this:

val aTuple = Tuple2(2, "hello, Scala")

Or, you can actually create tuples with just parentheses:

val aTuple = (2, "hello, Scala")

Tuples can group at most 22 elements of different types. The reason for the number of elements being 22, is because they are used in conjunction with Function Types.

Tuple Operations

You can access members of a tuple like so:

println(aTuple._1)

_1 is the first element, _2 is the second element etc.

We can create copies, just as we did with case classes:

println(aTuple.copy(_2 = "hello, James"))

There is also a swap method, that swaps the elements in place:

println(aTuple.swap)

Maps

Maps are collections used to associate things with other things. With maps, we associate keys to values. Keys are the index, values are the data corresponding to those keys:

val aMap: Map[String, Int] = Map()

In the map above, String is the key and Int is the value. We are instantiating the Map() by calling the apply method.

Putting Tuples into a Map

Let’s create a map, in which we put tuples into the map.

val phonebook = Map(("James", 555), ("Bob", 666))

You populate the map by specifying tuples, or pairings.

For a clearer, syntactic sugar for the pairings, you can also write the tuples with an -> instead:

val phonebook = Map("James" -> 555, "Bob" -> 666)

If we print out the map now, it will look like this:

println(phonebook) // prints out: 'Map(James -> 555, Bob -> 666)'

Map Operations

We can query a map if it has a key. The contains method takes a key and returns a boolean:

println(phonebook.contains("James"))

To obtain the value associated with the key, we can call the apply method:

println(phonebook.apply("James"))

Or we can leave the apply word out altogether:

println(phonebook("James"))

If you try to call the apply method on a map with a key that does not exist, the program will crash with an exception:

println(phonebook("Ted"))

This throws the exception:

Exception in thread “main” java.util.NoSuchElementException: key not found: Ted

A way to guard against throwing these exceptions, is to create the map with the .withDefaultValue() method:

val phonebook = Map(("James", 555), "Bob" -> 666).withDefaultValue(-1)

To add a pairing to the map, first we create the pairing:

val newPairing = "Mary" -> 678

And then we add the pairing to the phonebook, by creating a new phonebook and concatenating:

val newPhonebook = phonebook + newPairing

We use this technique of creating a new object, because maps are immutable.

Functionals on Map

There are several different functionals that we can use on Map.

Map, Flatmap and Filter

Just like we did with lists, we can use map, flatmap and filter on maps.

For map, the argument that is supplied is a pairing which is a tuple:

println(phonebook.map(pair => pair._1.toLowerCase -> pair._2)) // returns: Map(james -> 555, bob -> 666)

What the above says is that, for every pairing in the map, transform the map by lowercasing just the keys.

With Maps in Scala, the methods map, flatmap and filter all take pairings like this.

Filter Keys

A more useful method for maps is filterKeys:

println(phonebook.filterKeys(x => x.startsWith("J")))

The above will print the mapping where the keys are filtered by the predicate x => x.startsWith("J").

This could be written in shorthand like this:

println(phonebook.filterKeys(_.startsWith("J")))

Map Values

Another map specific function that is very useful is mapValues:

println(phonebook.mapValues(number => number * 10))

So here the lambda takes the value type (in this case Int) and returns something else (in this case 10 times the number).

As another example, You could use this to put some text in front of the number:

println(phonebook.mapValues(number => "0845-" + number )) // returns: Map(James -> 0845-555, Bob -> 0845-666)

Convertings Maps to Other Collections

We can put all the pairings from a map into a list:

println(phonebook.toList) // returns: List((James,555), (Bob,666))

Conversions can also happen vice versa. We pass in a tuple for a single element and convert that into a map:

println(List(("Daniel", 555)).toMap) // returns: Map(Daniel -> 555)

Something else very useful is the groupBy functionality:

val names = List("Bob", "James", "Angela", "Mary", "Daniel", "Jim")
println(names.groupBy(name => name.charAt(0)))

This will create a map, that will group every element that has the same first character into a single list.

So the above returns:

Map(J -> List(James, Jim), A -> List(Angela), M -> List(Mary), B -> List(Bob), D -> List(Daniel))

groupBy is very used in practice for grouping data.


Source Code

As always, the source code for this post is available on Github.