Jing Huang
Jing Huang

Reputation: 41

Convert Seq[Try[Option(String, Any)]] into Try[Option[Map[String, Any]]]

How to conveniently convert Seq[Try[Option[String, Any]]] into Try[Option[Map[String, Any]]].

If any Try before convert throws an exception, the converted Try should throw as well.

Upvotes: 0

Views: 408

Answers (5)

jrook
jrook

Reputation: 3519

The following solutions is based on this answer to the point that almost makes the question a duplicate.

Method 1: Using recursion

def trySeqToMap1[X,Y](trySeq : Seq[Try[Option[(X, Y)]]]) : Try[Option[Map[X,Y]]] = {

  def helper(it : Iterator[Try[Option[(X,Y)]]], m : Map[X,Y] = Map()) : Try[Option[Map[X,Y]]] = {
    if(it.hasNext) {
      val x = it.next()
      if(x.isFailure)
        Failure(x.failed.get)
      else if(x.get.isDefined)
        helper(it, m + (x.get.get._1-> x.get.get._2))
      else
        helper(it, m)
    } else Success(Some(m))
  }

  helper(trySeq.iterator)
}

Method 2: directly pattern matching in case you are able to get a stream or a List instead:

  def trySeqToMap2[X,Y](trySeq : LazyList[Try[Option[(X, Y)]]], m : Map[X,Y]= Map.empty[X,Y]) : Try[Option[Map[X,Y]]] =
    trySeq match {
      case Success(Some(h)) #:: tail => trySeqToMap2(tail, m + (h._1 -> h._2))
      case Success(None) #:: tail => tail => trySeqToMap2(tail, m)
      case Failure(f) #:: _ => Failure(f)
      case _ => Success(Some(m))
    }

note: this answer was previously using different method signatures. It has been updated to conform to the signature given in the question.

Upvotes: -1

Andrei T.
Andrei T.

Reputation: 2480

One way to approach this is by using a foldLeft:

// Let's say this is the object you're trying to convert
val seq: Seq[Try[Option[(String, Any)]]] = ???

seq.foldLeft(Try(Option(Map.empty[String, Any]))) {
  case (acc, e) =>
    for {
      accOption  <- acc
      elemOption <- e
    } yield elemOption match {
      case Some(value) => accOption.map(_ + value)
      case None        => accOption
    }
}

You start off with en empty Map. You then use a for comprehension to go through the current map and element and finally you add a new tuple in the map if present.

Upvotes: 0

Tim
Tim

Reputation: 27356

Assuming that the input type has a tuple inside the Option then this should give you the result you want:

val in: Seq[Try[Option[(String, Any)]]] = ???

val out: Try[Option[Map[String,Any]]] = Try(Some(in.flatMap(_.get).toMap))

If any of the Trys is Failure then the outer Try will catch the exception raised by the get and return Failure

The Some is there to give the correct return type

The get extracts the Option from the Try (or raises an exception)

Using flatMap rather than map removes the Option wrapper, keeping all Some values and discaring None values, giving Seq[(String, Any)]

The toMap call converts the Seq to a Map

Upvotes: 2

David Maze
David Maze

Reputation: 158908

If you're willing to use a functional support library like Cats then there are two tricks that can help this along:

  1. Many things like List and Try are traversable, which means that (if Cats's implicits are in scope) they have a sequence method that can swap two types, for example converting List[Try[T]] to Try[List[T]] (failing if any of the items in the list are failure).

  2. Almost all of the container types support a map method that can operate on the contents of a container, so if you have a function from A to B then map can convert a Try[A] to a Try[B]. (In Cats language they are functors but the container-like types in the standard library generally have map already.)

Cats doesn't directly support Seq, so this answer is mostly in terms of List instead.

Given that type signature, you can iteratively sequence the item you have to in effect push the list type down one level in the type chain, then map over that container to work on its contents. That can look like:

import cats.implicits._
import scala.util._

def convert(listTryOptionPair: List[Try[Option[(String, Any)]]]): Try[
  Option[Map[String, Any]]
] = {
  val tryListOptionPair = listTryOptionPair.sequence
  tryListOptionPair.map { listOptionPair =>
    val optionListPair = listOptionPair.sequence
    optionListPair.map { listPair =>
      Map.from(listPair)
    }
  }
}

https://scastie.scala-lang.org/xbQ8ZbkoRSCXGDJX0PgJAQ has a slightly more complete example.

Upvotes: 0

jq170727
jq170727

Reputation: 14655

Here is something that's not very clean but may help get you started. It assumes Option[(String,Any)], returns the first Failure if there are any in the input Seq and just drops None elements.

foo.scala

package foo
import scala.util.{Try,Success,Failure}

object foo {
  val x0 = Seq[Try[Option[(String, Any)]]]()
  val x1 = Seq[Try[Option[(String, Any)]]](Success(Some(("A",1))), Success(None))
  val x2 = Seq[Try[Option[(String, Any)]]](Success(Some(("A",1))), Success(Some(("B","two"))))
  val x3 = Seq[Try[Option[(String, Any)]]](Success(Some(("A",1))), Success(Some(("B","two"))), Failure(new Exception("bad")))
  def f(x: Seq[Try[Option[(String, Any)]]]) =
    x.find( _.isFailure ).getOrElse( Success(Some(x.map( _.get ).filterNot( _.isEmpty ).map( _.get ).toMap)) )
}

Example session

bash-3.2$ scalac foo.scala
bash-3.2$ scala -classpath .
Welcome to Scala 2.13.1 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_66).
Type in expressions for evaluation. Or try :help.

scala> import foo.foo._
import foo.foo._

scala> f(x0)
res0: scala.util.Try[Option[Equals]] = Success(Some(Map()))

scala> f(x1)
res1: scala.util.Try[Option[Equals]] = Success(Some(Map(A -> 1)))

scala> f(x2)
res2: scala.util.Try[Option[Equals]] = Success(Some(Map(A -> 1, B -> two)))

scala> f(x3)
res3: scala.util.Try[Option[Equals]] = Failure(java.lang.Exception: bad)

scala> :quit

Upvotes: 0

Related Questions