Categories
Tech

Scala Saturday – The filter Method

Another frequently used operation in functional programming is filter. The filter method allows you to pare down a collection by specifying a criterion. Any element in the collection that does not meet that criterion is dropped, and you get a new collection consisting only of the elements that meet the criterion.

Sequential Collections

Suppose you have a list of numbers, and you want to filter out the even numbers, leaving only the odds. Define a predicate—a function that takes an input and returns true or false—that determines whether an integer is odd, returning true for odd numbers and false for even. Pass that predicate to your list’s filter method, and List.filter returns a new list containing only the odd numbers:

Illustrates filtering evens out of a list of integers (4, 5, 6, 7) with a predicate that returns true for odd numbers and false for even numbers. This yields the list (5, 7).
Filtering Out the Even Numbers from a List of Numbers

Here is the code:

val f = (n: Int) => n % 2 == 1
// odds: List[Int] = List(5, 7)

Notice that filter preserves the order of the original collection because a list is a sequential collection. The same goes for arrays and sequences.

Sets

While filter works essentially the same for sets as it does for the sequential collections, it does not guarantee to preserve the order. Such is the nature of sets: they are not sequential collections.

If you have a set of words, and you want to retain only the words that do not start with “w,” you take the same approach as with a list. Define a predicate that determines whether a string starts with a letter other than "w". Then pass your predicate to your set’s filter method:

Illustrates filtering a set of words { "weary", "world", "young", "struggle" } to retain only the words that do not begin with "w": { "young", "struggle" }
Retaining Only Words that Don’t Start with “w”

The code ought to look pretty familiar:

val f = (s: String) => !s.startsWith("w")
val nonWWords = Set(
  "weary","world","young","struggle"
) filter f
// nonWWords: scala.collection.immutable.Set[String] =
//   Set(young, struggle)

Again, while the order of the elements happens to have been preserved in this simple example with a small set, Set.filter is not required to do so.

Maps

There is also Map.filter. Instead of taking a single-input predicate, Map.filter takes a two-input predicate: the first input is the map’s key type, and the second is the value type. That means you can filter a map on the key, the value, or both.

Maybe you have a map of Spock’s Beard albums to the members of the band who played on that album. (Spock’s Beard has had some changes in personnel over the years.) You want to know on which albums Ted Leonard was on vocals:

val albums = Map(
  "The Light" -> Set("N. Morse","A. Morse","Meros","D'Virgillio"),
  "Beware of Darkness" -> Set("N. Morse","A. Morse","Okumoto","Meros","D'Virgillio"),
  "The Kindness of Strangers" -> Set("N. Morse","A. Morse","Okumoto","Meros","D'Virgillio"),
  "Day for Night" -> Set("N. Morse","A. Morse","Okumoto","Meros","D'Virgillio"),
  "V" -> Set("N. Morse","A. Morse","Okumoto","Meros","D'Virgillio"),
  "Snow" -> Set("N. Morse","A. Morse","Okumoto","Meros","D'Virgillio"),
  "Feel Euphoria" -> Set("A. Morse","Okumoto","Meros","D'Virgillio"),
  "Octane" -> Set("A. Morse","Okumoto","Meros","D'Virgillio"),
  "Spock's Beard" -> Set("A. Morse","Okumoto","Meros","D'Virgillio"),
  "X" -> Set("A. Morse","Okumoto","Meros","D'Virgillio"),
  "Brief Nocturnes and Dreamless Sleep" -> Set("Leonard","A. Morse","Okumoto","Meros","Keegan")
)
val withLeonard = albums filter { case (_,v) =>
  v contains "Leonard"
}
// withLeonard: scala.collection.immutable.Map[String,scala.collection.immutable.Set[String]] =
//   Map(
//     Brief Nocturnes and Dreamless Sleep ->
//     Set(A. Morse, Leonard, Okumoto, Keegan, Meros))

That’s a big chunk there, but the long and the short is that our filter function is ignoring the key (hence the underscore) and examining only the value, searching the set in each entry for Leonard’s name.

The Map trait also defines a filterKeys method for filtering by key. filterKeys takes a single-input predicate like Set.filter.

Filter … NOT!

In addition to filter, Scala collections also define the filterNot method. It simply takes the filtering function and inverts it so that you get results opposite those of filter.

It’s a matter of style, I suppose, as to whether you prefer to use filterNot or to use filter with a filtering function that you invert yourself. There is an argument to be made that filterNot is more readable: it is sometimes easy to overlook that little ! in a larger expression. Still, it seems like an unnecessary expansion of the API. Furthermore, if you’re going to have both filter and filterNot, why do the collections not provide, e.g., findNot, dropWhileNot, and takeWhileNot—the inverted counterparts of find, dropWhile, and takeWhile?

One reply on “Scala Saturday – The filter Method”

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.