2

USE CASE

I have a list of files that can might have a valid mime type or not. In my code, I represent this using an Option.

I need to convert a Seq[Option[T]] to Option[Seq[T]] so that I do not process the list if some of the files are invalid.

ERROR

This is the error in the implementation below:

found   : (Option[Seq[A]], Option[A]) => Option[Seq[A]]
[error]  required: (Option[Any], Option[Any]) => Option[Any]
[error]     s.fold(init)(liftOptionItem[A])

IMPLEMENTATION

def liftOptionItem[A](acc: Option[Seq[A]], itemOption: Option[A]): Option[Seq[A]] = {
    {
      acc match {
        case None => None
        case Some(items) =>
          itemOption match {
            case None => None
            case Some(item) => Some(items ++ Seq(item))
          }
      }
    }
  }

  def liftOption[A](s: Seq[Option[A]]): Option[Seq[A]] = {
    s.fold(Some(Seq()))(liftOptionItem[A])
  }

This implementation returns Option[Any] instead of Option[Seq[A] as the type of the liftOptionItem[A] does not fit in.

vamsiampolu
  • 6,328
  • 19
  • 82
  • 183
  • this seems a duplicate topic to a stackerflow, please refer to https://stackoverflow.com/questions/28753777/converting-listoptiona-to-an-optionlista-in-scala concept is the same than here.... – legramira Sep 28 '18 at 00:59
  • @legramira thanks. Could you close this question as a duplicate, thanks. Aologies for not searching before asking this question – vamsiampolu Sep 28 '18 at 01:05
  • `Some(acc.map(_.get))` – Seraf Sep 28 '18 at 01:18
  • 3
    Possible duplicate of [Converting List\[Option\[A\]\] to an Option\[List\[A\]\] in Scala](https://stackoverflow.com/questions/28753777/converting-listoptiona-to-an-optionlista-in-scala) – Brian McCutchon Sep 28 '18 at 02:50

6 Answers6

4

If you use TypeLevel Cats:

import cats.implicits._

List(Option(1), Option(2), Option(3)).traverse(identity)

Returns:

Option[List[Int]] = Some(List(1, 2, 3))

You have to use List so use a toList first:

Seq(Option(1), Option(2), Option(3)).toList.traverse(identity).map(_.toSeq)
Daniel Hinojosa
  • 972
  • 5
  • 9
  • Where do I import from or how do I declare `identity`? – Nefedov Efim Nov 21 '18 at 13:57
  • @NefedovEfim, `identity` is automatic from `Predef` which is automatically imported in Scala. So all you need is `import cats.implicits._`. If anything perhaps you may need to qualify the traverse, `Seq(Option(1), Option(2), Option(3)).toList.traverse[Option, Int](identity).map(_.toSeq)` but that's all you need, and of course, install cats into your project. ;) – Daniel Hinojosa Nov 22 '18 at 02:58
3

using scalaz:

import scalaz._
import Sclaza._

val x:List[Option[Int]] = List(Option(1))
x.sequence[Option, Int] //returns Some(List(1))

val y:List[Option[Int]] = List(None, Option(1))
y.sequence[Option, Int] // returns None
azuras
  • 247
  • 2
  • 9
2

If you dont want to use functional libraries like cats or Scalaz you could use a foldLeft

def seqToOpt[A](seq: Seq[Option[A]]): Option[Seq[A]] =
    seq.foldLeft(Option(Seq.empty[A])){
        (res, opt) =>
          for {
            seq <- res
            v <- opt
          } yield seq :+ v
      }
Mikel San Vicente
  • 3,831
  • 2
  • 21
  • 39
0

Tail-recursive Solution: It returns None if any one of the seq element is None.

def seqToOption[T](s: Seq[Option[T]]): Option[Seq[T]] = {
  @tailrec
  def seqToOptionHelper(s: Seq[Option[T]], accum: Seq[T] = Seq[T]()): Option[Seq[T]] = {
    s match {
      case Some(head) :: Nil => Option(head +: accum)
      case Some(head) :: tail => seqToOptionHelper(tail, head +: accum)
      case _ => None
    }
  }
  seqToOptionHelper(s)
}
Shankar Shastri
  • 1,134
  • 11
  • 18
0

Dealing with None in case statements is the reason for returning the Option[Seq[Any]] type in stead of Option[Seq[A]] type. We need to make the function liftOptionItem[A] to return Option[Seq[Any]] type. And the compilation error can be fixed with the following changes in both the functions.(Because fold does not go in any particular order, there are constraints on the start value and thus return value , the foldLeft is used in stead of fold.)

 def liftOptionItem[A](acc: Option[Seq[Any]], itemOption: Option[A]): Option[Seq[Any]] = {
     {
       acc match {
         case None => Some(Nil)
         case Some(items)=>
           itemOption match {
             case None => Some(items ++ Seq("None"))
             case Some(item) => Some(items ++ Seq(item))
           }
       }
     }
   }

 def liftOption[A](s: Seq[Option[A]]): Option[Seq[Any]] = {
   s.foldLeft(Option(Seq[Any]()))(liftOptionItem[A])
 }

Now, code compiles.

In Scala REPL:

scala> val list1 = Seq(None,Some(21),None,Some(0),Some(43),None)
list1: Seq[Option[Int]] = List(None, Some(21), None, Some(0), Some(43), None)

scala> liftOption(list1)
res2: Option[Seq[Any]] = Some(List(None, 21, None, 0, 43, None))

scala> val list2 = Seq(None,Some("String1"),None,Some("String2"),Some("String3"),None)
list2: Seq[Option[String]] = List(None, Some(String1), None, Some(String2), Some(String3), None)

scala> liftOption(list2)
res3: Option[Seq[Any]] = Some(List(None, String1, None, String2, String3, None))
RAGHHURAAMM
  • 1,099
  • 7
  • 15
0

There is no really "beautiful" way to make this with out scalaz or cats. But you can try something like this.

def seqToOpt[A](seq: Seq[Option[A]]): Option[Seq[A]] = {
  val flatten = seq.flatten
  
  if (flatten.isEmpty) None
  else Some(flatten)
}