Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scala foldLeft while some conditions are true

How to emulate following behavior in Scala? i.e. keep folding while some certain conditions on the accumulator are met.

def foldLeftWhile[B](z: B, p: B => Boolean)(op: (B, A) => B): B

For example

scala> val seq = Seq(1, 2, 3, 4)
seq: Seq[Int] = List(1, 2, 3, 4)
scala> seq.foldLeftWhile(0, _ < 3) { (acc, e) => acc + e }
res0: Int = 1
scala> seq.foldLeftWhile(0, _ < 7) { (acc, e) => acc + e }
res1: Int = 6

UPDATES:

Based on @Dima answer, I realized that my intention was a little bit side-effectful. So I made it synchronized with takeWhile, i.e. there would be no advancement if the predicate does not match. And add some more examples to make it clearer. (Note: that will not work with Iterators)

like image 914
ntviet18 Avatar asked Dec 11 '18 16:12

ntviet18


People also ask

How does foldLeft work in Scala?

The foldLeft method takes an associative binary operator function as parameter and will use it to collapse elements from the collection. The order for traversing the elements in the collection is from left to right and hence the name foldLeft. The foldLeft method allows you to also specify an initial value.

What is foldLeft and foldRight in Scala?

In Scala, we can use foldLeft and foldRight methods for collection types like List . Both methods recursively combine items into another item. foldLeft combines items from left one to right one, on the other hand foldRight does this from right one to left one.

What is reduceLeft in Scala?

2.2. reduceLeft is used to reduce a collection by applying a function to each element in order to combine them and return a single result. Let's have a look at the reduceLeft signature: def reduceLeft[B >: A](op: (B, A) ⇒ B): B.

How does fold left work?

foldLeft() method is a member of TraversableOnce trait, it is used to collapse elements of collections. It navigates elements from Left to Right order. It is primarily used in recursive functions and prevents stack overflow exceptions.


2 Answers

First, note that your example seems wrong. If I understand correctly what you describe, the result should be 1 (the last value on which the predicate _ < 3 was satisfied), not 6

The simplest way to do this is using a return statement, which is very frowned upon in scala, but I thought, I'd mention it for the sake of completeness.

def foldLeftWhile[A, B](seq: Seq[A], z: B, p: B => Boolean)(op: (B, A) => B): B = foldLeft(z) { case (b, a) => 
   val result = op(b, a) 
   if(!p(result)) return b
   result
}

Since we want to avoid using return, scanLeft might be a possibility:

seq.toStream.scanLeft(z)(op).takeWhile(p).last

This is a little wasteful, because it accumulates all (matching) results. You could use iterator instead of toStream to avoid that, but Iterator does not have .last for some reason, so, you'd have to scan through it an extra time explicitly:

 seq.iterator.scanLeft(z)(op).takeWhile(p).foldLeft(z) { case (_, b) => b }
like image 158
Dima Avatar answered Sep 23 '22 19:09

Dima


It is pretty straightforward to define what you want in scala. You can define an implicit class which will add your function to any TraversableOnce (that includes Seq).

implicit class FoldLeftWhile[A](trav: TraversableOnce[A]) {
  def foldLeftWhile[B](init: B)(where: B => Boolean)(op: (B, A) => B): B = {
    trav.foldLeft(init)((acc, next) => if (where(acc)) op(acc, next) else acc)
  }
}
Seq(1,2,3,4).foldLeftWhile(0)(_ < 3)((acc, e) => acc + e)

Update, since the question was modified:

implicit class FoldLeftWhile[A](trav: TraversableOnce[A]) {
  def foldLeftWhile[B](init: B)(where: B => Boolean)(op: (B, A) => B): B = {
    trav.foldLeft((init, false))((a,b) => if (a._2) a else {
      val r = op(a._1, b)
      if (where(r)) (op(a._1, b), false) else (a._1, true)
    })._1
  }
}

Note that I split your (z: B, p: B => Boolean) into two higher-order functions. That's just a personal scala style preference.

like image 33
Jack Davidson Avatar answered Sep 20 '22 19:09

Jack Davidson