patternMinor
Early-termination foldLeft for Scala Streams
Viewed 0 times
scalaterminationearlyforstreamsfoldleft
Problem
The
Test:
It prints "run!" only 3 times.
One question arises immediately: how do I generalize this to all
All suggestions are welcome, of course.
foldRight and foldLeft methods for Scala Streams cannot terminate early, so here is my attempt to provide that functionality through the "enrich my library" pattern: implicit class StreamWithLazyFold[A](s: Stream[A]) {
def foldLeft1[B](z: B)(f: (B, A) => (B, Boolean)): B = s match {
case _ if s.isEmpty => z
case h #:: t => {
val (z1, stop) = f(z, h.asInstanceOf[A])
if(stop) z1
else t.asInstanceOf[Stream[A]].foldLeft1(z1)(f)
}
}
}Test:
s.foldLeft1(true)((x, y) => {
println("run!")
val b = (y < 3) && x
(b, !b)
})It prints "run!" only 3 times.
One question arises immediately: how do I generalize this to all
Seqs?All suggestions are welcome, of course.
Solution
Your
The second
If we want to generalize your
We cannot use pattern matching if we are using
We can use
We can adjust the code of your
Which we can use :
Note that this doesn't work for
foldLeft1 method looks fine. - You don't need
asInstanceOf. You pattern match aStream[A]so the head will always beAand the tailStream[A].
- A
caseblock doesn't need parentheses, but this is mostly a choice of preferred style.
The second
case block could then look like :case h #:: t =>
val (z1, stop) = f(z, h)
if (stop) z1 else t.foldLeft1(z1)(f)If we want to generalize your
foldLef1 method, we can see that foldLeft is implemented in TraversableOnce for Stream, List, ... (source on GitHub). So we could create an implicit class for TraversableOnce.We cannot use pattern matching if we are using
TraversableOnce to get the current element and the rest elements. If we look at the implementation of foldLeft for inspiration, we can see that it uses foreach and a mutable variable. That is no good in our case since we don't (necessarily) want to loop over all the elements of our collection (or iterator, ...).We can use
toIterator to get an Iterator to loop over our elements, and with an Iterator we can stop iterating whenever we want, that looks just like we want.We can adjust the code of your
foldLeft1 for Stream to use an Iterator :implicit class FoldLazy[A](trav: TraversableOnce[A]) {
def foldLeft1[B](z: B)(f: (B, A) => (B, Boolean)) = {
def go(it: Iterator[A], z: B): B =
if (it.hasNext) {
val (z1, stop) = f(z, it.next)
if (stop) z1 else go(it, z1)
} else z
go(trav.toIterator, z)
}
}Which we can use :
Stream(1,2,3).foldLeft1(0)((acc, elem) => (acc + elem, elem % 2 == 0)) // 3
List(1,2,3).foldLeft1(0)((acc, elem) => (acc + elem, elem % 2 == 0)) // 3
Vector(1,2,3).foldLeft1(0)((acc, elem) => (acc + elem, elem % 2 == 0)) // 3Note that this doesn't work for
Array, since Array is only a TraversableOnce through ArrayOps or WrappedArray, and the Scala compiler doesn't apply multiple (nested) implicit conversions.Code Snippets
case h #:: t =>
val (z1, stop) = f(z, h)
if (stop) z1 else t.foldLeft1(z1)(f)implicit class FoldLazy[A](trav: TraversableOnce[A]) {
def foldLeft1[B](z: B)(f: (B, A) => (B, Boolean)) = {
def go(it: Iterator[A], z: B): B =
if (it.hasNext) {
val (z1, stop) = f(z, it.next)
if (stop) z1 else go(it, z1)
} else z
go(trav.toIterator, z)
}
}Stream(1,2,3).foldLeft1(0)((acc, elem) => (acc + elem, elem % 2 == 0)) // 3
List(1,2,3).foldLeft1(0)((acc, elem) => (acc + elem, elem % 2 == 0)) // 3
Vector(1,2,3).foldLeft1(0)((acc, elem) => (acc + elem, elem % 2 == 0)) // 3Context
StackExchange Code Review Q#108803, answer score: 4
Revisions (0)
No revisions yet.