Has ever happened to you that you have a collection and you want to split it into two parts: one that satisfies certain assertion and the other one that doesn’t? In that case, do you resort to use a filter and a filterNot? Don’t worry, in this
teleshopping ad post, we’ll see some not-so-popular but common methods for splitting collections.
Traversable[A] collection, we have the following method:
def takeWhile(p: A => Boolean): Traversable[A]
It gets as parameter the condition that has to be checked by the first N elements to be collected from current collection. Don’t worry, an example illustrates it better:
val numbers = List(2, 4, 5, 6, 7) val firstEven = numbers.takeWhile(_ % 2 == 0) //List(2, 4)
As you can see, the list you get is the result of getting the first elements in the collection while the given assertion is checked.
Like his elder brother,
dropWhile receives as parameter a function, but its behavior is based on removing all elements from the beginning until the given condition is not checked.
val names = List("Julio", "Jose", "Alberto", "Javier") val survivors = names.dropWhile(_.startsWith("J")) //List("Alberto","Javier")
Even though there are some other elements in the list that check the condition, method
dropWhile only drop the first N elements as long as they all check the condition. At the very first moment the assertion is not validated, the method stop removing elements.
But as we were talking at the introduction, what happens if I want to apply one of this functions without loosing the remaining elements in the collection? In that case,
span is your friend.
Its signature is:
def span(p: A => Boolean): (Traversable[A], Traversable[A])
And the way it works, just to picture it, is returning, for a
t collection and a constraint(function)
(t takeWhile f, t dropWhile f), but quoting Scala api) “possibly[sic] more efficient than”.
Examples, examples everywhere…
case class Event(timeStamp: Long) val events: Stream[Event] = ??? val systemCrashTimestamp: Long = ??? val (eventsBeforeCrash,eventsAfterCrash) = events.span(_.timeStamp <= systemCrashTimeStamp)
In this example, we’re modeling possible events that may happen to a system. By reading some
Stream, we access all events that occurred to the system to monitorize. On the other hand, we’re notified that some fatal-terrible error take place in the system (
For splitting events that took place before the death-fatal-error, from the other that happened later; we can use
span (et voilà!)
Ok then, if you looked closer before with
dropWhile examples, a lil’ problem could be inferred: if you split collections this way, takeWhile only took first elements that checked the condition, but not all of them.
A first logical approach (that you may have used at some point), is to write something like this:
val numbers = List(2, 3, 4, 5, 6, 7) val isEven: Int => Boolean = _ % 2 == 0 val even = numbers.filter(isEven) val odd = numbers.filterNot(isEven)
Not bad. But like method
span, we can think about
partition like a method that, given a collection called
t and a function
f, behaves as follows:
(t filter f, t filterNot f); making implementation much easier (and “possibly[sic] more efficient than”):
val numbers = List(2, 3, 4, 5, 6, 7) val isEven: Int => Boolean = _ % 2 == 0 val (even, odd) = numbers.partition(isEven)
Until next tip.