How to filter a Scala Map (filterKeys, retain, transform)

This is an excerpt from the Scala Cookbook (partially modified for the internet). This is Recipe 11.22, “How to Filter a Scala Map”

Problem

You want to filter the elements contained in a Scala Map, either by directly modifying a mutable map, or by applying a filtering algorithm on an immutable map to create a new map.

Solution

Use the retain method to define the elements to retain when using a mutable map, and use filterKeys or filter to filter the elements in a mutable or immutable map, remembering to assign the result to a new variable.

Filtering mutable maps

You can filter the elements in a mutable map using the retain method to specify which elements should be retained:

scala> var x = collection.mutable.Map(1 -> "a", 2 -> "b", 3 -> "c")
x: scala.collection.mutable.Map[Int,String] = Map(2 -> b, 1 -> a, 3 -> c)

scala> x.retain((k,v) => k > 1)
res0: scala.collection.mutable.Map[Int,String] = Map(2 -> b, 3 -> c)

scala> x
res1: scala.collection.mutable.Map[Int,String] = Map(2 -> b, 3 -> c)

As shown, retain modifies a mutable map in place. As implied by the anonymous function signature used in that example:

(k,v) => ...

your algorithm can test both the key and value of each element to decide which elements to retain in the map.

In a related note, the transform method doesn’t filter a map, but it lets you transform the elements in a mutable map:

scala> x.transform((k,v) => v.toUpperCase)
res0: scala.collection.mutable.Map[Int,String] = Map(2 -> B, 3 -> C)

scala> x
res1: scala.collection.mutable.Map[Int,String] = Map(2 -> B, 3 -> C)

Depending on your definition of “filter,” you can also remove elements from a map using methods like remove and clear, which are shown in Recipe 11.16.

Mutable and immutable maps

When working with a mutable or immutable map, you can use a predicate with the filterKeys methods to define which map elements to retain. When using this method, remember to assign the filtered result to a new variable:

scala> val x = Map(1 -> "a", 2 -> "b", 3 -> "c")
x: scala.collection.mutable.Map[Int,String] = Map(2 -> b, 1 -> a, 3 -> c)

scala> val y = x.filterKeys(_ > 2)
y: scala.collection.Map[Int,String] = Map(3 -> c)

The predicate you supply should return true for the elements you want to keep in the new collection and false for the elements you don’t want.

If your algorithm is longer, you can define a function (or method), and then use it in the filterKeys call, rather than using an anonymous function. First define your method, such as this method, which returns true when the value the method is given is 1:

scala> def only1(i: Int) = if (i == 1) true else false
only1: (i: Int)Boolean

Then pass the method to the filterKeys method:

scala> val x = Map(1 -> "a", 2 -> "b", 3 -> "c")
x: scala.collection.mutable.Map[Int,String] = Map(2 -> b, 1 -> a, 3 -> c)

scala> val y = x.filterKeys(only1)
y: scala.collection.Map[Int,String] = Map(1 -> a)

In an interesting use, you can also use a Set with filterKeys to define the elements to retain:

scala> var m = Map(1 -> "a", 2 -> "b", 3 -> "c")
m: scala.collection.immutable.Map[Int,String] = Map(1 -> a, 2 -> b, 3 -> c)

scala> val newMap = m.filterKeys(Set(2,3))
newMap: scala.collection.immutable.Map[Int,String] = Map(2 -> b, 3 -> c)

You can also use all of the filtering methods that are shown in Chapter 10. For instance, the Map version of the filter method lets you filter the map elements by either key, value, or both. The filter method provides your predicate a Tuple2, so you can access the key and value as shown in these examples:

scala> var m = Map(1 -> "a", 2 -> "b", 3 -> "c")
m: scala.collection.immutable.Map[Int,String] = Map(1 -> a, 2 -> b, 3 -> c)

// access the key
scala> m.filter((t) => t._1 > 1)
res0: scala.collection.immutable.Map[Int,String] = Map(2 -> b, 3 -> c)

// access the value
scala> m.filter((t) => t._2 == "c")
res1: scala.collection.immutable.Map[Int,String] = Map(3 -> c)

The take method lets you “take” (keep) the first N elements from the map:

scala> m.take(2)
res2: scala.collection.immutable.Map[Int,String] = Map(1 -> a, 2 -> b)

See the filtering recipes in Chapter 10 for examples of other methods that you can use, including takeWhile, drop, slice, and more.

I hope it has been helpful. All the best, Al.