How to yield a single element from for loop in scala?
Asked Answered
S

9

21

Much like this question:

Functional code for looping with early exit

Say the code is

def findFirst[T](objects: List[T]):T = {
  for (obj <- objects) {
    if (expensiveFunc(obj) != null) return /*???*/ Some(obj)
  }
  None
}

How to yield a single element from a for loop like this in scala?

I do not want to use find, as proposed in the original question, i am curious about if and how it could be implemented using the for loop.

* UPDATE *

First, thanks for all the comments, but i guess i was not clear in the question. I am shooting for something like this:

val seven = for {
    x <- 1 to 10
    if x == 7
} return x

And that does not compile. The two errors are: - return outside method definition - method main has return statement; needs result type

I know find() would be better in this case, i am just learning and exploring the language. And in a more complex case with several iterators, i think finding with for can actually be usefull.

Thanks commenters, i'll start a bounty to make up for the bad posing of the question :)

Saied answered 12/11, 2012 at 12:13 Comment(4)
The code example is precisely returning a single element from a for loop. What do you want to do differently?Gregory
Change the return type to Option[T] and the code you've written will work. Not sure I understand your question.Gibbon
I'd advise you to use the version with find and also avoid the use of null and return to make your code more idiomatic Scala.Actinometer
Totally agree with Jesper. Your question sounds like: yeah, I know how to solve this problem in a concise and obvious way, but could anyone teach me to make this unidiomatic and tangled? There's no better solution for this problem than the one based on `find.Hypothetical
C
26

If you want to use a for loop, which uses a nicer syntax than chained invocations of .find, .filter, etc., there is a neat trick. Instead of iterating over strict collections like list, iterate over lazy ones like iterators or streams. If you're starting with a strict collection, make it lazy with, e.g. .toIterator.

Let's see an example.

First let's define a "noisy" int, that will show us when it is invoked

def noisyInt(i : Int) = () => { println("Getting %d!".format(i)); i }

Now let's fill a list with some of these:

val l = List(1, 2, 3, 4).map(noisyInt)

We want to look for the first element which is even.

val r1 = for(e <- l; val v = e() ; if v % 2 == 0) yield v

The above line results in:

Getting 1!
Getting 2!
Getting 3!
Getting 4!
r1: List[Int] = List(2, 4)

...meaning that all elements were accessed. That makes sense, given that the resulting list contains all even numbers. Let's iterate over an iterator this time:

val r2 = (for(e <- l.toIterator; val v = e() ; if v % 2 == 0) yield v)

This results in:

Getting 1!
Getting 2!
r2: Iterator[Int] = non-empty iterator

Notice that the loop was executed only up to the point were it could figure out whether the result was an empty or non-empty iterator.

To get the first result, you can now simply call r2.next.

If you want a result of an Option type, use:

if(r2.hasNext) Some(r2.next) else None

Edit Your second example in this encoding is just:

val seven = (for {
    x <- (1 to 10).toIterator
    if x == 7
} yield x).next

...of course, you should be sure that there is always at least a solution if you're going to use .next. Alternatively, use headOption, defined for all Traversables, to get an Option[Int].

Carmacarmack answered 12/11, 2012 at 12:49 Comment(4)
That is close, is it possible to write this so that the type of the value returned is actually a single Int, instead of Vector[Int] or List[Int]?Saied
The result type is an Iterator in my example. Call .next on it to get an Int (will throw an exception if there were no solutions).Carmacarmack
I see... is there a method that returns None instead of exception? (and Some[Int] if found). If not, it would be doable in a "OptionIterator" trait of my own and a implicit conversion, right?Saied
There used to be a method 'headOpt' but it got deprecated ages ago. You can indeed redefine it yourself with an implicit conversion.Carmacarmack
A
18

You can turn your list into a stream, so that any filters that the for-loop contains are only evaluated on-demand. However, yielding from the stream will always return a stream, and what you want is I suppose an option, so, as a final step you can check whether the resulting stream has at least one element, and return its head as a option. The headOption function does exactly that.

def findFirst[T](objects: List[T], expensiveFunc: T => Boolean): Option[T] =
    (for (obj <- objects.toStream if expensiveFunc(obj)) yield obj).headOption
Albumenize answered 16/11, 2012 at 15:19 Comment(0)
A
3

Why not do exactly what you sketched above, that is, return from the loop early? If you are interested in what Scala actually does under the hood, run your code with -print. Scala desugares the loop into a foreach and then uses an exception to leave the foreach prematurely.

Alverson answered 12/11, 2012 at 12:32 Comment(0)
X
2

So what you are trying to do is to break out a loop after your condition is satisfied. Answer here might be what you are looking for. How do I break out of a loop in Scala?.

Overall, for comprehension in Scala is translated into map, flatmap and filter operations. So it will not be possible to break out of these functions unless you throw an exception.

Xiphisternum answered 12/11, 2012 at 12:32 Comment(0)
F
1

If you are wondering, this is how find is implemented in LineerSeqOptimized.scala; which List inherits

override /*IterableLike*/
  def find(p: A => Boolean): Option[A] = {
    var these = this
    while (!these.isEmpty) {
      if (p(these.head)) return Some(these.head)
      these = these.tail
    }
    None
  }
Freethinker answered 12/11, 2012 at 12:44 Comment(0)
S
1

This is a horrible hack. But it would get you the result you wished for.

Idiomatically you'd use a Stream or View and just compute the parts you need.

def findFirst[T](objects: List[T]): T = {

def expensiveFunc(o : T)  = // unclear what should be returned here

case class MissusedException(val data: T) extends Exception

try {
  (for (obj <- objects) {
    if (expensiveFunc(obj) != null) throw new MissusedException(obj)
  })
  objects.head // T must be returned from loop, dummy
} catch {
  case MissusedException(obj) => obj
}

}

Steib answered 12/11, 2012 at 12:54 Comment(1)
Return from closures are implemented like that.Afroasian
G
1

Why not something like

object Main {     
  def main(args: Array[String]): Unit = {
    val seven = (for (
    x <- 1 to 10
    if x == 7
    ) yield x).headOption
  }
}

Variable seven will be an Option holding Some(value) if value satisfies condition

Gingras answered 19/11, 2012 at 13:16 Comment(1)
This (using 'if' in for comprehensions) is actually a good idea, and should be the first thing to consider. However, it's not always possible - there are cases where multiple matches would be created, and the second level trick (presented in @dapek's answer) is to do lazy evaluation on them.Physiologist
T
0

I hope to help you.

I think ... no 'return' impl.

object TakeWhileLoop extends App {
    println("first non-null: " + func(Seq(null, null, "x", "y", "z")))

    def func[T](seq: Seq[T]): T = if (seq.isEmpty) null.asInstanceOf[T] else
        seq(seq.takeWhile(_ == null).size)
}

object OptionLoop extends App {
    println("first non-null: " + func(Seq(null, null, "x", "y", "z")))

    def func[T](seq: Seq[T], index: Int = 0): T = if (seq.isEmpty) null.asInstanceOf[T] else
        Option(seq(index)) getOrElse func(seq, index + 1)
}

object WhileLoop extends App {
    println("first non-null: " + func(Seq(null, null, "x", "y", "z")))

    def func[T](seq: Seq[T]): T = if (seq.isEmpty) null.asInstanceOf[T] else {
        var i = 0
        def obj = seq(i)
        while (obj == null)
            i += 1
        obj
    }
}
Turbinal answered 12/11, 2012 at 16:19 Comment(0)
O
0
objects iterator filter { obj => (expensiveFunc(obj) != null } next

The trick is to get some lazy evaluated view on the colelction, either an iterator or a Stream, or objects.view. The filter will only execute as far as needed.

Ordzhonikidze answered 15/11, 2012 at 16:30 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.