9

I have a piece of code which would code as follows:

val e2 = for (e <- elements if condition(expensiveFunction(e))) yield {
            expensiveFunction(e)
         }

Where the condition will be true for a few elements and then become false for all remaining ones.

Unfortunately this doesn't work (even if I ignore performance) because my elements is an infinite iterator.

Is there a way to use a "break" in a for-comprehension so it stops yielding elements when a certain condition holds? Otherwise, what would be the scala-idiomatic way to compute my e2?

tharindu_DG
  • 8,900
  • 6
  • 52
  • 64
jsalvata
  • 2,155
  • 15
  • 32
  • possible duplicate of [How do I break out of a loop in Scala?](http://stackoverflow.com/questions/2742719/how-do-i-break-out-of-a-loop-in-scala) – om-nom-nom Nov 28 '12 at 01:43
  • That question is about a regular loop, not a for-comprehension (i.e., with yield). I suspect takeWhile can be part of the solution... – jsalvata Nov 28 '12 at 01:55
  • The question is somehow different, but answers (as far as I understand) can be applied to your situation as well – om-nom-nom Nov 28 '12 at 02:00
  • for-comprehensions are completely different than traditional loops under the covers. The fact that they share similar syntax is to make them easier to read. – rancidfishbreath Nov 28 '12 at 02:01
  • The duplicate is http://stackoverflow.com/questions/13343531/how-to-yield-a-single-element-from-for-loop-in-scala – som-snytt Nov 28 '12 at 05:48
  • That's not a duplicate either, as it asks for only one item, not a few like this one. – jsalvata Nov 28 '12 at 14:14
  • Possible duplicate of [How do I break out of a loop in Scala?](http://stackoverflow.com/questions/2742719/how-do-i-break-out-of-a-loop-in-scala) – eje Mar 06 '17 at 22:02
  • Yes, the solution is the same, but that question is ill-phrased: a "for (<-)" is a comprehension, not a loop. – jsalvata Mar 17 '17 at 12:01

5 Answers5

20

You could go with lazy approach:

val e2 = elements.toIterator                    
          .map(expensiveFunction)
          .takeWhile(result => result == true) // or just .takeWhile(identity)
// you may want to strict iterator, (e.g. by calling .toList) at the end

So you compute expensiveFunction on demand, and if there is false on some step, you won't do unnecessary work.

om-nom-nom
  • 62,329
  • 13
  • 183
  • 228
  • +1 -- still, because my particular case is more clearly written as a for-comprehension, I'm accepting som-snytt's answer. Thanks. – jsalvata Nov 28 '12 at 08:57
  • 1
    This may actually be the *best* answer: the one from @som-snytt invokes the `expensiveFunction` for all elements - and only *afterwards* discards the unwanted results. – WestCoastProjects Sep 16 '17 at 21:40
  • @javadba No, for comprehension desugars to map. I think my answer was to the question about `for`? Otherwise they are the same. – som-snytt Sep 18 '17 at 16:59
  • I used *may* in a polite manner but actually I had *tested* this and *verified* that your approach is not greedy whereas that from @som-snytt does incur full cost of the *expensiveFunction* even on unused/unneeded elements. – WestCoastProjects Sep 18 '17 at 18:24
  • The recent comments about lazy/strict are interesting because `elements` is already an iterator, per the question. – som-snytt Sep 19 '17 at 15:53
9
scala> def compute(i: Int) = { println(s"f$i"); 10*i }
compute: (i: Int)Int

scala> for (x <- Stream range (0, 20)) yield compute(x)
f0
res0: scala.collection.immutable.Stream[Int] = Stream(0, ?)

scala> res0 takeWhile (_ < 100)
res1: scala.collection.immutable.Stream[Int] = Stream(0, ?)

scala> res1.toList
f1
f2
f3
f4
f5
f6
f7
f8
f9
f10
res2: List[Int] = List(0, 10, 20, 30, 40, 50, 60, 70, 80, 90)

Edit, another demonstration:

scala> def compute(i: Int) = { println(s"f$i"); 10*i }
compute: (i: Int)Int

scala> for (x <- Stream range (0, 20)) yield compute(x)
f0
res0: scala.collection.immutable.Stream[Int] = Stream(0, ?)

scala> res0 takeWhile (_ < 100)
res1: scala.collection.immutable.Stream[Int] = Stream(0, ?)

scala> res1.toList
f1
f2
f3
f4
f5
f6
f7
f8
f9
f10
res2: List[Int] = List(0, 10, 20, 30, 40, 50, 60, 70, 80, 90)

scala> Stream.range(0,20).map(compute).toList
f0
f1
f2
f3
f4
f5
f6
f7
f8
f9
f10
f11
f12
f13
f14
f15
f16
f17
f18
f19
res3: List[Int] = List(0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160, 170, 180, 190)

scala> Stream.range(0,20).map(compute).takeWhile(_ < 100).toList
f0
f1
f2
f3
f4
f5
f6
f7
f8
f9
f10
res4: List[Int] = List(0, 10, 20, 30, 40, 50, 60, 70, 80, 90)
som-snytt
  • 39,429
  • 2
  • 47
  • 129
  • 1
    In my terminology, that is `(for(e <- elements) yield expensiveFunction(e)).takeWhile(r => condition(r))`. I am surprised at how obvious it is. Looks like reasoning about infinite iterators is still difficult for me. – jsalvata Nov 28 '12 at 08:57
  • @jsalvata note (for(...)...) takeWhile condition. Placeholder syntax, leaving off the placeholder because of expected type. And losing the dot and parens. Others have got me hooked on minimalism. – som-snytt Nov 28 '12 at 17:23
  • This seems to compute the `expensiveFunction` for *all* elements - and truncate the resulting sequence only *after* the computation. – WestCoastProjects Sep 16 '17 at 21:37
  • @javadba What do you observe that suggests that? Compare `Stream.range(0,20).map(compute).toList` and `Stream.range(0,20).map(compute).takeWhile(_ < 100).toList`. The collection is not realized after each operation from left to right. – som-snytt Sep 18 '17 at 16:56
  • @javadba it depends on the collection used. For strict collections it will compute all elements, for lazy ones it will not. – puhlen Sep 18 '17 at 18:54
  • I tested this on a non-lazy collection - by adding print statements before each return value: the `expensiveFucnction` gets invoked on all elements and then the result is truncated. That is *not* the case for the `toIterator` approach from @om-nom-nom: that one correctly invokes expensiveFunction` only the min required times. – WestCoastProjects Sep 18 '17 at 19:37
  • @javadba Stream is lazy, is the point. I hope that clears it up. As you can see, your previous assertion about my example is not correct. – som-snytt Sep 18 '17 at 20:41
  • @javadba sorry, I just looked at the question again, the point was how to take a prefix, so actually strict was beside the point. In the comments, there are a couple of near-duplicates that didn't satisfy the OP. – som-snytt Sep 18 '17 at 21:11
  • OK it's more clear now : the answer shown here is intended for lazy collections. It's not clear to me what the OP intended: but in any case useful to have the distinction of answers supporting both approaches to collections . I will leave my comment above since i can not edit it - and deleting it would lose the context of the thread. – WestCoastProjects Sep 18 '17 at 21:30
3

You can just use takeWhile:

elements.takeWhile(condition(expensiveFunction(_)))
Jakozaur
  • 1,957
  • 3
  • 18
  • 20
  • I could use `elements.takeWhile(e => condition(expensiveFunction(e))`, but then I would need to re-compute the expensiveFunction for each of them again. – jsalvata Nov 28 '12 at 01:52
0

Found this solution:

(for (e <- elements) yield {
  val x= expensiveFunction(e)
  if (condition(x)) Some(x) else None
}).takeWhile(_.nonEmpty).map(_.get)

A better one, anyone?

jsalvata
  • 2,155
  • 15
  • 32
0

Here is my idea: If the elements is a lazy container already(like Stream, Iterator):

(for (e <- elements; 
      buf = expensiveFunction(e);  
      if condition(buf)) yield buf).headOption

Or not:

(for (e <- elements.view; 
      buf = expensiveFunction(e);  
      if condition(buf)) yield buf).headOption
Eastsun
  • 18,526
  • 6
  • 57
  • 81