9

I'm trying to fold over a list of Options in order return the first(or last) Some value or None if there aren't any Some values.

scala> val opts = List(None, Some(1), None, Some(2), None)
opts: List[Option[Int]] = List(None, Some(1), None, Some(2), None)

scala> opts foldLeft(None)((a,io) => a match { case None => io; case Some(i) =>
a})
<console>:9: error: object None does not take parameters
              opts foldLeft(None)((a,io) => a match { case None => io; case Some
(i) => a})
                                 ^

Not sure what I'm doing wrong. Also there is probably a way to do this simpler using a higher order function but nothing from here caught my eye.

Trevor
  • 955
  • 9
  • 16

5 Answers5

16

Maybe this can solve your problem - the first element:

opts.flatten.headOption

And the last element:

opts.flatten.lastOption

flatten method will unbox all Option values in the list and drop all None values. headOption/lastOption will return either Some for the first/last element in the list or None if list is empty.

tenshi
  • 26,268
  • 8
  • 76
  • 90
9

tenshi’s answer is pretty straightforward but for long lists it will attempt to flatten everything as it isn’t lazy. (I think view won’t help us here either but I’m not quite sure.)

In that case, you could use:

opts.dropWhile(_.isEmpty).headOption.flatMap(identity)

Unfortunately, we cannot use flatten here as this will return a generic Iterable[Int] and no Option, so we have to chose the longer idiom flatMap(identity).

Edit: As dave noticed:

opts.find(_.isDefined).flatMap(identity)

would be even better.

Community
  • 1
  • 1
Debilski
  • 66,976
  • 12
  • 110
  • 133
  • It seems like find returns the first it finds. Is there a way to find the nth one or the last one in the list? – Trevor Nov 07 '11 at 17:32
  • I think for the last one, you’d have to reverse the list before. – Debilski Nov 07 '11 at 17:47
  • `opts.dropWhile(_.isEmpty).headOption` returns `Option[Option[Int]]`, right?! But why does a `flatten` on that double option not return `Option[Int]`? [ScalaDoc-Option](http://www.scala-lang.org/archives/downloads/distrib/files/nightly/docs/library/index.html#scala.Option) states by `ev: <:<[A, Option[B]]` that it should work with `A = Option[Int]` in our case. – Peter Schmitz Nov 07 '11 at 18:25
  • 1
    @PeterSchmitz: You’re correct for the nightly build which the ScalaDoc is referring to. This will indeed be fixed for 2.10 (unless, of course, they revert the change for some reason). On the other hand in Scala 2.9 there is no method `flatten` on `Option` – only an implicit conversion to `Iterable` which gets executed in this case. – Debilski Nov 07 '11 at 21:47
  • @Debilski Ah, great. I was confused while looking at nightly docs an having my code (2.9.1) react other than expected. Of course one should take the appropriate docs ;) – Peter Schmitz Nov 07 '11 at 23:08
  • @Trevor The *efficient* way to get the last occurrence, it seems, is to define your collection as a `Vector` rather than a `List` thus: `val opts = Vector(...)` then use a `reverseIterator` to count back from the end: `opts.reverseIterator.find(_.isDefined).flatMap(identity)`. – Luigi Plinge Nov 08 '11 at 04:10
4

There are better ways to do it, but to answer the question as posed, you have two problems

1) You are missing the . following opts. You can only use infix notation to convert a.m(b) to a m b. The foldLeft method is of the form a.m(b)(c). So either write it like that, or include parentheses (a m b)(c).

2) You need to parameterize None as an Option[Int]: it's being interpreted here as the None object, rather than the value of an Option[Int] instance.

So this will work:

opts.foldLeft(None: Option[Int])(
  (a,io) => a match { case None => io; case Some(i) => a } )
Luigi Plinge
  • 50,650
  • 20
  • 113
  • 180
2

Why go to such trouble?

opts.find(_.nonEmpty).flatten
opts.reverse.find(_.nonEmpty).flatten
Daniel C. Sobral
  • 295,120
  • 86
  • 501
  • 681
  • 1
    Because I'm still learning scala ... also is there a way to find the nth value? – Trevor Nov 07 '11 at 19:02
  • 1
    @Trevor - `opts.view.filter(_.nonEmpty).drop(n-1).headOption.flatten` is a way to find the `n`th item without having to flatten the whole list. – Rex Kerr Nov 07 '11 at 19:53
  • It should probably be pointed out that `flatten` will return a `List` rather than an `Option` as the other answers do. – Luigi Plinge Nov 08 '11 at 04:13
  • @LuigiPlinge You mean in the "nth" value case? Yeah, that example did not work. It would throw an exception, and return `List`. I removed it. But the code in the answer returns `Option` as expected. – Daniel C. Sobral Nov 08 '11 at 12:55
  • Hmm, not for me it doesn't, in 2.9.0: it returns `Iterable[Int] = List(2)`. From Debilski's answer in his question, maybe this is a difference between 2.9 and 2.10. – Luigi Plinge Nov 08 '11 at 16:00
  • @LuigiPlinge How disagreeable. Indeed, on trunk `Option` has a `flatten` method. – Daniel C. Sobral Nov 08 '11 at 20:01
0

Starting Scala 2.9, you can use collectFirst to extract the first defined option:

List(None, Some(1), None, Some(2), None).collectFirst { case Some(x) => x }
// Option[Int] = Some(1)

this will stop iterating at the first defined Option and avoids flattening the whole List before looking for its head.


Starting Scala 2.13 you can use findLast, which is the reverse of find (while waiting for a possible collectLast?)

List(None, Some(1), None, Some(2), None).findLast(_.isDefined).flatten
// Option[Int] = Some(2)
Xavier Guihot
  • 54,987
  • 21
  • 291
  • 190