【问题标题】:Best way to turn a Lists of Eithers into an Either of Lists?将一个列表转换为一个列表的最佳方法?
【发布时间】:2011-06-27 07:01:22
【问题描述】:

我有一些类似下面的代码,其中我有一个列表,我想把它变成列表中的一个...特别是(在这种情况下),如果列表中有任何左,然后我返回它们列表的左,否则我返回权利列表的右。

val maybe: List[Either[String, Int]] = getMaybe
val (strings, ints) = maybe.partition(_.isLeft)
strings.map(_.left.get) match {
  case Nil => Right(ints.map(_.right.get))
  case stringList => Left(stringList)
}

打电话给get 总是让我觉得我一定错过了什么。

有没有更惯用的方法来做到这一点?

【问题讨论】:

  • 我发现所描述的问题有点奇怪。 Strings 对 Ints 的偏好似乎有点不对称。如何编写类似 unzipEither 的东西,它返回一个字符串列表和一个整数列表。这样,该方法就不会丢失信息,而您的版本在混合列表的情况下会这样做。
  • 听起来你最好使用像Scalaz Validation这样的结构。

标签: scala either


【解决方案1】:

Scala 2.13 开始,大多数集合现在都提供了partitionMap 方法,该方法根据返回RightLeft 的函数对元素进行分区。

在我们的例子中,我们甚至不需要将输入转换为RightLeft 的函数来定义分区,因为我们已经有了Rights 和Lefts。因此简单地使用identity!

那么这只是一个根据是否有任何左边来匹配生成的左右分区元组的问题:

eithers.partitionMap(identity) match {
  case (Nil, rights) => Right(rights)
  case (lefts, _)    => Left(lefts)
}
// * List[Either[String, Int]] = List(Right(3), Left("error x"), Right(7))
//         => Either[List[String],List[Int]] = Left(List(error x))
// * List[Either[String, Int]] = List(Right(3), Right(7))
//         => Either[List[String],List[Int]] = Right(List(3, 7))

对于partitionMap的理解,这里是中间步骤的结果:

List(Right(3), Left("error x"), Right(7)).partitionMap(identity)
// (List[String], List[Int]) = (List(error x), List(3, 7))

【讨论】:

    【解决方案2】:

    Scala 函数式编程一书中的解决方案。

    def sequence[E,A](es: List[Either[E,A]]): Either[E,List[A]] = 
        traverse(es)(x => x)
    
    def traverse[E,A,B](es: List[A])(f: A => Either[E, B]): Either[E, List[B]] = 
        es match {
          case Nil => Right(Nil)
          case h::t => (f(h) map2 traverse(t)(f))(_ :: _)
        }
    def map2[EE >: E, B, C](a: Either[E, A], b: Either[EE, B])(f: (A, B) => C): 
       Either[EE, C] = for { a1 <- a; b1 <- b } yield f(a1,b1)
    

    【讨论】:

    • traverse 也可以使用foldRight 编写
    【解决方案3】:
    val list = List(Left("x"),Right(2), Right(4))
    val strings = for (Left(x) <- list) yield(x)
    val result = if (strings.isEmpty) Right(for (Right(x) <- list) yield(x)) 
                 else Left(strings)
    

    【讨论】:

      【解决方案4】:

      如果你想拥有更通用和更实用的东西,那么来自猫库的Validated 是你可能想要的类型。它类似于 Either 可以聚合错误。与NonEmptyList 结合使用会非常强大。

      http://typelevel.org/cats/datatypes/validated.html

      【讨论】:

        【解决方案5】:

        难道不是更优雅的方式吗?

          def flatten[E,A](es: List[Either[E,A]]): Either[E,List[A]] = {
            @tailrec
            def go(tail: List[Either[E,A]], acc: List[A]): Either[E,List[A]] = tail match {
              case Nil  => Right(acc)
              case h::t => h match {
                case Left(e)  => Left(e)
                case Right(a) => go(t, a :: acc)
              }
            }
            go(es, Nil) map { _ reverse }
          }
        
        • 尾递归
        • 一次通过,假设 a :: acc 是快速子弹
        • 但是,最终还是要反转
        • partitionMap,可能更快,因为基于构建器的内部实现
        • 但是这个是懒惰的。你会立即离开。

        【讨论】:

          【解决方案6】:

          我有点不想要任何因果报应,因为它是 Chris's answer 和来自 here 的 Viktor 的合并......但这里有一个替代方案:

          def split[CC[X] <: Traversable[X], A, B](xs: CC[Either[A, B]])
             (implicit bfa: CanBuildFrom[Nothing, A, CC[A]], bfb: CanBuildFrom[Nothing, B, CC[B]]) : (CC[A], CC[B]) =
            xs.foldLeft((bfa(), bfb())) {
              case ((as, bs), l@Left(a)) => (as += a, bs)
              case ((as, bs), r@Right(b)) => (as, bs += b)
            } match {
              case (as, bs) => (as.result(), bs.result())
            }
          

          例子:

          scala> val eithers: List[Either[String, Int]] = List(Left("Hi"), Right(1))
          eithers: List[Either[String,Int]] = List(Left(Hi), Right(1))
          
          scala> split(eithers)
          res0: (List[String], List[Int]) = (List(Hi),List(1))
          

          【讨论】:

            【解决方案7】:

            您可以编写split 的通用版本,如下所示:

            def split[X, CC[X] <: Traversable[X], A, B](l : CC[Either[A, B]])
               (implicit bfa : CanBuildFrom[Nothing, A, CC[A]], bfb : CanBuildFrom[Nothing, B, CC[B]]) : (CC[A], CC[B]) = {
              def as = {
                val bf = bfa()
                bf ++= (l collect { case Left(x) => x})
                bf.result
              }
            
              def bs = {
                val bf = bfb()
                bf ++= (l collect { case Right(x) => x})
                bf.result
              }
            
              (as, bs)
            }
            

            这样:

            scala> List(Left("x"),Right(2), Right(4)) : List[Either[java.lang.String,Int]]
            res11: List[Either[java.lang.String,Int]] = List(Left(x), Right(2), Right(4))
            
            scala> split(res11)
            res12: (List[java.lang.String], List[Int]) = (List(x),List(2, 4))
            
            scala> Set(Left("x"),Right(2), Right(4)) : Set[Either[java.lang.String,Int]]
            res13: Set[Either[java.lang.String,Int]] = Set(Left(x), Right(2), Right(4))
            
            scala> split(res13)
            res14: (Set[java.lang.String], Set[Int]) = (Set(x),Set(2, 4))
            

            【讨论】:

            • 出于好奇,为什么不创建 asbs 作为 vals?
            【解决方案8】:
            data.partition(_.isLeft) match {                            
              case (Nil,  ints) => Right(for(Right(i) <- ints) yield i)        
              case (strings, _) => Left(for(Left(s) <- strings) yield s)
            }
            

            一次通过:

            data.partition(_.isLeft) match {                            
              case (Nil,  ints) => Right(for(Right(i) <- ints.view) yield i)        
              case (strings, _) => Left(for(Left(s) <- strings.view) yield s)
            }
            

            【讨论】:

            • 嗯,这可以说是更清楚了。不过,这似乎做了更多的工作。很遗憾你不能一次完成。
            • 如果您使用视图而不是列表作为结果类型,您可以一次性完成,请参阅我的编辑。
            • 我想我必须给 .view 把戏颁奖——谢谢 Viktor。
            • 你能解释一下为什么你认为应该这样做吗?
            【解决方案9】:

            分别提取左右:

            val data: List[Either[String, Int]] = List(
              Right(1), 
              Left("Error #1"), 
              Right(42), 
              Left("Error #2")
            )
            
            
            val numbers: List[Int] = data.collect { case Right(value) => value }
            val errors: List[String] = data.collect { case Left(error) => error }
            
            
            println(numbers) // List(1, 42)
            println(errors)  // List(Error #1, Error #2)
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2015-08-13
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2021-10-16
              • 2016-05-27
              • 2012-09-29
              • 1970-01-01
              相关资源
              最近更新 更多