If everyone is posting their version, I'll add my own, too.
Uses foldLeft
to traverse the list just once and Vector
for efficient functional append. The final result is a List[List[A]]
, though:
/**
* scala> val l = List(0, 10, 12, 7, -10, 7, 2, 3, -2, 4)
* scala> groupFilter(l)(_ >= 0)
* res0: List[List[Int]] = List(List(0, 10, 12, 7), List(7, 2, 3), List(4))
*/
def groupFilter[A](list: List[A])(predicate: A => Boolean): List[List[A]] = {
// The accumulator is a tuple containing the already grouped
// previous values and the the current group being built.
val seed = Vector.empty[List[A]] -> Vector.empty[A]
val (prevGroups, lastGroup) = list.foldLeft(seed) {
case (groups @ (prevGroups, lastGroup), a) =>
if (predicate(a))
prevGroups -> (lastGroup :+ a)
else if (lastGroup.nonEmpty)
(prevGroups :+ lastGroup.toList) -> Vector.empty
else
groups
}
(prevGroups :+ lastGroup.toList).toList
}
And a version that uses foldRight
and List
's ::
to keep time complexity low:
def groupFilter[A](list: List[A])(predicate: A => Boolean): List[List[A]] = {
val seed = List.empty[List[A]] -> List.empty[A]
val (prevGroups, lastGroup) = list.foldRight(seed) {
case (a, groups @ (prevGroups, lastGroup)) =>
if (predicate(a))
prevGroups -> (a :: lastGroup)
else if (lastGroup.nonEmpty)
(lastGroup :: prevGroups) -> List.empty
else
groups
}
lastGroup :: prevGroups
}
One last version using explicit tail-recursion:
def groupFilter[A](list: List[A])(predicate: A => Boolean): List[List[A]] = {
@annotation.tailrec
def loop(list: List[A], prevGroups: Vector[List[A]], lastGroup: Vector[A]): List[List[A]] = {
list match {
case Nil =>
(prevGroups :+ lastGroup.toList).toList
case head :: tail if predicate(head) =>
loop(tail, prevGroups, lastGroup :+ head)
case _ :: tail if lastGroup.nonEmpty =>
loop(tail, prevGroups :+ lastGroup.toList, Vector.empty)
case _ :: tail =>
loop(tail, prevGroups, lastGroup)
}
}
loop(list, Vector.empty, Vector.empty)
}