【问题标题】:Convert Seq[Try[Option(String, Any)]] into Try[Option[Map[String, Any]]]将 Seq[Try[Option(String, Any)]] 转换为 Try[Option[Map[String, Any]]]
【发布时间】:2020-01-30 23:33:07
【问题描述】:

如何方便地将 Seq[Try[Option[String, Any]]] 转换为 Try[Option[Map[String, Any]]]。

如果转换之前的任何 Try 抛出异常,则转换后的 Try 也应该抛出。

【问题讨论】:

  • 我认为Option[String,Any] 没有意义。 Option 采用单个类型参数。你是说Option[(String,Any)] 吗?
  • 我不明白Try[Option[SOMETHING]] 的意思。选项是NoneSome,表示无效值在到达Try 时已被处理。好像有点多余。
  • @jrook 有一个场景你想跟踪一个操作是成功还是失败,即使这个操作成功了,它也可以返回一个 None 或 Some。
  • @texasbruce 如果我控制签名并从操作中获取None,我已经知道发生了一些不好的事情。如果我想知道发生这种情况的原因,那么我会直接Try 操作。一个操作产生 None 而没有任何错误的情况对我来说似乎是可疑的。为什么我想要一个可以产生None 作为正常值的逻辑?如果您能向我展示一个真实的用例,我将不胜感激。
  • @jrook 有很多情况,比如你从 db 中查询一条记录。您使用 Try 来指示数据库或连接是否有问题。然后用 Some 或 None 表示记录是否存在。

标签: scala


【解决方案1】:

假设输入类型在Option 中有一个元组,那么这应该会给你想要的结果:

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

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

如果Trys 中的任何一个是Failure,那么外部Try 将捕获get 引发的异常并返回Failure

Some 用于提供正确的返回类型

getTry 中提取Option(或引发异常)

使用flatMap 而不是map 删除Option 包装,保留所有Some 值并丢弃None 值,给出Seq[(String, Any)]

toMap 调用将Seq 转换为Map

【讨论】:

    【解决方案2】:

    这里有些东西不是很干净,但可以帮助您入门。它假定Option[(String,Any)],如果输入Seq 中有任何Failure,则返回第一个None 元素。

    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)) )
    }
    

    示例会话

    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
    

    【讨论】:

      【解决方案3】:

      如果您愿意使用像 Cats 这样的功能支持库,那么有两个技巧可以帮助您:

      1. ListTry 这样的很多东西都是traversable,这意味着(如果Cats 的隐含在范围内)它们有一个sequence 方法可以交换两种类型,例如转换@987654329 @ 到 Try[List[T]](如果列表中的任何项目失败,则失败)。

      2. 几乎所有的容器类型都支持map方法,可以对容器的内容进行操作,所以如果你有一个从AB的函数,那么map可以转换一个@ 987654335@ 到Try[B]。 (在 Cats 语言中,它们是 functors,但标准库中的类容器类型通常已经有 map。)

      Cats doesn't directly support Seq,所以这个答案主要是List

      鉴于该类型签名,您可以迭代地sequence 您必须将列表类型在类型链中有效地向下推一级,然后map 在该容器上以处理其内容。这可能看起来像:

      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 有一个稍微完整的例子。

      【讨论】:

        【解决方案4】:

        解决此问题的一种方法是使用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
            }
        }
        

        你从一个空地图开始。然后,您使用 for comprehension 遍历当前地图和元素,最后在地图中添加一个新元组(如果存在)。

        【讨论】:

          【解决方案5】:

          以下解决方案基于this answer,以至于几乎使问题重复。

          方法一:使用递归

          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)
          }
          

          方法2:直接模式匹配,以防您能够获得流或List

            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))
              }
          

          注意:这个答案之前使用了不同的方法签名。它已更新以符合问题中给出的签名。

          【讨论】:

            猜你喜欢
            • 2016-08-10
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2018-09-10
            • 2020-05-18
            • 1970-01-01
            相关资源
            最近更新 更多