1. Introduction

Filtering collections is a good skill to add to our programming toolbox as it’s a common task.

In this tutorial, we’ll go through different ways to filter None values from a Map in Scala.

2. Setup

Scala has two types of Maps – mutable and immutable. By default, Scala Maps are immutable. However, mutable Maps can be used through the following import:

scala> import scala.collection.mutable.Map
import scala.collection.mutable.Map

Let’s create an immutable Scala Map called xmen of type Map[Rank, Option[String]]:

scala> type Rank = Int
type Rank

scala> val xmen: Map[Rank,Option[String]] = Map(
     | (1,Some("ProfessorX")),
     | (2,Some("Wolverine")),
     | (3,None),
     | (4,Some("Night Crawler")),
     | (5,None))
val xmen: Map[Rank,Option[String]] = HashMap(5 -> None, 1 -> Some(ProfessorX), 2 -> Some(Wolverine), 3 -> None, 4 -> Some(Night Crawler))

In this section, we describe a Map called xmen that shows a sequence of ranks and associated members depicted by Rank and Option[String], respectively. Notice the xmen members with ranks 3 and 5 are portrayed by None values.

In the sections that follow, we’ll look at methods to filter out these None values from our xmen Map.

3. Filtering Mutable and Immutable Maps

Let’s look at methods that work on both mutable and immutable Maps. Each of these methods returns a new collection as a result of acting on the Map.

3.1. Using filter()

The filter() method takes a predicate:

scala> val filterMap = xmen.filter(_._2 != None)
val filterMap: scala.collection.immutable.Map[Rank,Option[String]] = HashMap(1 -> Some(ProfessorX), 2 -> Some(Wolverine), 4 -> Some(Night Crawler))

In the code above, filter()* checks if each element satisfies the predicate and keeps those that are *true. In other words, it retains the second Tuple elements that aren’t equal to None.** Notice the Tuple syntax that we employ in this example using _._2.

3.2. Using filterNot()

The filterNot() method acts as an inverse of the filter() method:

scala> val filterNotMap = xmen.filterNot(_._2 == None)
val filterNotMap: scala.collection.immutable.Map[Rank,Option[String]] = HashMap(1 -> Some(ProfessorX), 2 -> Some(Wolverine), 4 -> Some(Night Crawler))

Compared to filter(), the filterNot() method will keep elements that don’t satisfy the predicate*.*

3.3. Using collect()

The collect() method builds a new collection by applying a partial function to all elements of the Map:

scala> val collectMap = xmen.collect { case (k,Some(v)) => (k,Some(v)) }
val collectMap: scala.collection.immutable.Map[Rank,Some[String]] = HashMap(1 -> Some(ProfessorX), 2 -> Some(Wolverine), 4 -> Some(Night Crawler))

Here, key-value pairs that match a (k,Some(v)) pattern are retained, building a collection that only satisfies this case.

3.4. Using for Comprehension

The **for-comprehension can *act as a replacement for a flatMap(), Map sequence*:

scala> val forMap = for { (k,v) <- xmen if v != None } yield (k,v)
val forMap: scala.collection.immutable.Map[Rank,Option[String]] = HashMap(1 -> Some(ProfessorX), 2 -> Some(Wolverine), 4 -> Some(Night Crawler))

In this scenario, we extract (k,v) – a key-value pair from our xmen Map – only if the value isn’t equal to None. This is essentially a filter. We then collect these values back together into a Map using yield.

The next two methods are lazy in nature. They’re a good choice if we don’t want the overhead of creating another Map. They can, however, still perform filtering if used correctly.

3.5. Using withFilter()

The withFilter() method also takes a predicate like the previous methods but uniquely returns a FilterMonadic instead of a Map:

scala> val withFilterMap = xmen.withFilter(_._2 != None)
val withFilterMap: scala.collection.MapOps.WithFilter[Rank,Option[String],[x]scala.collection.immutable.Iterable[x],[x, y]scala.collection.immutable.Map[x,y]] = scala.collection.MapOps$WithFilter@1183d28e
scala> withFilterMap.map(x=>x)
val res2: scala.collection.immutable.Map[Rank,Option[String]] = HashMap(1 -> Some(ProfessorX), 2 -> Some(Wolverine), 4 -> Some(Night Crawler))

*A FilterMonadic restricts to the domain of map(), flatMap(), foreach(), and withFilter() operations.* This example employs the map() method to transform a FilterMonadic into a Map.

3.6. Using filterKeys()

The filterKeys() method also takes a predicate but returns a MapView:

scala> val filterKeysMap = xmen.view.filterKeys(xmen(_) != None)
val filterKeysMap: scala.collection.MapView[Rank,Option[String]] = MapView(<not computed>)
scala> filterKeysMap.toMap
val res3: scala.collection.immutable.Map[Rank,Option[String]] = Map(1 -> Some(ProfessorX), 2 -> Some(Wolverine), 4 -> Some(Night Crawler))

If we call a map with its key, it returns the associated value. We utilize this syntax in xmen(_) to filter Map values that don’t equal None. The filterKeys() method provides a lazy implementation until we call toMap(), which converts the MapView to a Map.

filterKeys() can also take a Set of keys as an argument to filter a Map:

scala> val filterKeysSet = xmen.view.filterKeys(Set(1,2,4)).toMap
val filterKeysSet: scala.collection.immutable.Map[Rank,Option[String]] = Map(1 -> Some(ProfessorX), 2 -> Some(Wolverine), 4 -> Some(Night Crawler))

This is a less programmatic way of filtering a Map but it might still come in handy.

4. Filtering Mutable Maps

Here, we’ll look at methods that work only with mutable Maps.

4.1. Using filterInPlace()

Let’s construct a mutable Map for this example:

scala> import scala.collection.mutable
import scala.collection.mutable

scala> val xmenMutable: mutable.Map[Rank,Option[String]] = mutable.Map(
     | (1,Some("ProfessorX")),
     | (2,Some("Wolverine")),
     | (3,None),
     | (4,Some("Night Crawler")),
     | (5,None))
val xmenMutable: scala.collection.mutable.Map[Rank,Option[String]] = HashMap(1 -> Some(ProfessorX), 2 -> Some(Wolverine), 3 -> None, 4 -> Some(Night Crawler), 5 -> None)

Mutable Maps can be filtered in place using the filterInPlace() method, which checks if each element satisfies a predicate:

scala> xmenMutable.filterInPlace((_,v) => v != None)
val res4: xmenMutable.type = HashMap(1 -> Some(ProfessorX), 2 -> Some(Wolverine), 4 -> Some(Night Crawler))

The filterInPlace() method doesn’t create a new Map but, instead, filters out None values from the Map it’s acting on*.*

5. Conclusion

In this article, we’ve looked at seven methods of filtering out None values from a Map. Keep in mind that some of these methods are available for other Scala collections and can be applied the same way.

As always, the code for this article can be found over on GitHub.


» 下一篇: ScalaPy 介绍