diff --git a/src/main/scala/scala/collection/decorators/IterableDecorator.scala b/src/main/scala/scala/collection/decorators/IterableDecorator.scala index 579943b..470c088 100644 --- a/src/main/scala/scala/collection/decorators/IterableDecorator.scala +++ b/src/main/scala/scala/collection/decorators/IterableDecorator.scala @@ -19,6 +19,21 @@ class IterableDecorator[C, I <: IsIterable[C]](coll: C)(implicit val it: I) { def foldSomeLeft[B](z: B)(op: (B, it.A) => Option[B]): B = it(coll).iterator.foldSomeLeft(z)(op) + /** Lazy left to right fold. Like `foldLeft` but the combination function `op` is + * non-strict in its second parameter. If `op(b, a)` chooses not to evaluate `a` and + * returns `b`, this terminates the traversal early. + * + * @param z the start value + * @param op the binary operator + * @tparam B the result type of the binary operator + * @return the result of inserting `op` between consecutive elements of the + * collection, going left to right with the start value `z` on the left, + * and stopping when all the elements have been traversed or earlier if + * `op(b, a)` choose not to evaluate `a` and returns `b` + */ + def lazyFoldLeft[B](z: B)(op: (B, => it.A) => B): B = + it(coll).iterator.lazyFoldLeft(z)(op) + /** * Right to left fold that can be interrupted before traversing the whole collection. * @param z the start value diff --git a/src/main/scala/scala/collection/decorators/IteratorDecorator.scala b/src/main/scala/scala/collection/decorators/IteratorDecorator.scala index 02b5af4..c444ef5 100644 --- a/src/main/scala/scala/collection/decorators/IteratorDecorator.scala +++ b/src/main/scala/scala/collection/decorators/IteratorDecorator.scala @@ -52,6 +52,20 @@ class IteratorDecorator[A](val `this`: Iterator[A]) extends AnyVal { result } + def lazyFoldLeft[B](z: B)(op: (B, => A) => B): B = { + var result = z + var finished = false + while (`this`.hasNext && !finished) { + var nextEvaluated = false + val elem = `this`.next() + def getNext = { nextEvaluated = true; elem } + val acc = op(result, getNext) + finished = !nextEvaluated && acc == result + result = acc + } + result + } + def lazyFoldRight[B](z: B)(op: A => Either[B, B => B]): B = { def chainEval(x: B, fs: immutable.List[B => B]): B = diff --git a/src/test/scala/scala/collection/decorators/IterableDecoratorTest.scala b/src/test/scala/scala/collection/decorators/IterableDecoratorTest.scala index a3f6208..37a9b19 100644 --- a/src/test/scala/scala/collection/decorators/IterableDecoratorTest.scala +++ b/src/test/scala/scala/collection/decorators/IterableDecoratorTest.scala @@ -17,6 +17,24 @@ class IterableDecoratorTest { Assert.assertEquals(10, List[Int]().foldSomeLeft(10)((x, y) => Some(x + y))) } + @Test + def lazyFoldLeftIsStackSafe(): Unit = { + val bigList = List.range(1, 50000) + def sum(as: Iterable[Int]): Int = + as.lazyFoldLeft(0)(_ + _) + + Assert.assertEquals(sum(bigList), 1249975000) + } + + @Test + def lazyFoldLeftIsLazy(): Unit = { + val nats = LazyList.from(0) + def exists[A](as: Iterable[A])(f: A => Boolean): Boolean = + as.lazyFoldLeft(false)(_ || f(_)) + + Assert.assertTrue(exists(nats)(_ > 100000)) + } + @Test def lazyFoldRightIsLazy(): Unit = { val xs = LazyList.from(0) def chooseOne(x: Int): Either[Int, Int => Int]= if (x < (1 << 16)) Right(identity) else Left(x)