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:
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:
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”
[…] filter function takes a collection and a predicate and returns only the items in a collection that meet the […]