将 Seq[Either[String, Int]] 转换为 (Seq[String], Seq[Int]) 的高效 and/or 惯用方法

Efficient and/or idiomatic way to turn Seq[Either[String, Int]] to (Seq[String], Seq[Int])

稍微简化一下,我的问题来自一个字符串列表 input,我想用返回 Either[String,Int] 的函数 parse 解析它。

然后是 list.map(parse) returns Either 的列表。程序的下一步是格式化一条错误消息,总结所有错误 通过已解析整数列表。

让我们调用我正在寻找的解决方案 partitionEithers

通话中

partitionEithers(List(Left("foo"), Right(1), Left("bar")))

会给

(List("foo", "bar"),List(1))

最好在标准库中找到类似的东西。如果没有某种干净、惯用和高效的解决方案,那将是最好的。我也可以将某种有效的实用程序函数粘贴到我的项目中。

我在these 3 earlier questions之间很困惑。据我所知,这些问题都不符合我的情况,但那里的一些答案似乎包含对这个问题的有效答案。

这是模仿 Scala 集合内部结构样式的命令式实现。

我想知道是否应该有这样的东西,因为至少我 运行 不时地进入这个。

import collection._
import generic._
def partitionEithers[L, R, E, I, CL, CR]
                    (lrs: I)
                    (implicit evI: I <:< GenTraversableOnce[E],
                              evE: E <:< Either[L, R],
                              cbfl: CanBuildFrom[I, L, CL],
                              cbfr: CanBuildFrom[I, R, CR])
                    : (CL, CR) = {
  val ls = cbfl()
  val rs = cbfr()

  ls.sizeHint(lrs.size)
  rs.sizeHint(lrs.size)

  lrs.foreach { e => evE(e) match {
    case Left(l)  => ls += l
    case Right(r) => rs += r
  } }

  (ls.result(), rs.result())
}

partitionEithers(List(Left("foo"), Right(1), Left("bar"))) == (List("foo", "bar"), List(1))
partitionEithers(Set(Left("foo"), Right(1), Left("bar"), Right(1))) == (Set("foo", "bar"), Set(1))

Scala 集合提供 partition 函数:

val eithers: List[Either[String, Int]] = List(Left("foo"), Right(1), Left("bar"))

eithers.partition(_.isLeft) match {
  case (leftList, rightList) =>
    (leftList.map(_.left.get), rightList.map(_.right.get))
}

=> res0: (List[String], List[Int]) = (List(foo, bar),List(1))

更新

如果您想将它包装在一个(甚至可能在某种程度上更安全​​的)泛型函数中:

def partitionEither[Left : ClassTag, Right : ClassTag](in: List[Either[Left, Right]]): (List[Left], List[Right]) =
  in.partition(_.isLeft) match {
    case (leftList, rightList) =>
      (leftList.collect { case Left(l: Left) => l }, rightList.collect { case Right(r: Right) => r })
}

你可以使用 foldLeft。

  def f(s: Seq[Either[String, Int]]): (Seq[String], Seq[Int]) = {
    s.foldRight((Seq[String](), Seq[Int]())) { case (c, r) =>
      c match {
        case Left(le) => (le +: r._1, r._2)
        case Right(ri) => (r._1 , ri +: r._2)
      }
    }
  }

val eithers: List[Either[String, Int]] = List(Left("foo"), Right(1), Left("bar"))

scala> f(eithers)
res0: (Seq[String], Seq[Int]) = (List(foo, bar),List(1))

我真的不明白其他答案的扭曲程度。所以这是一个班轮:

scala> val es:List[Either[Int,String]] = 
           List(Left(1),Left(2),Right("A"),Right("B"),Left(3),Right("C"))
es: List[Either[Int,String]] = List(Left(1), Left(2), Right(A), Right(B), Left(3), Right(C))

scala> es.foldRight( (List[Int](), List[String]()) ) { 
         case ( e, (ls, rs) ) => e.fold( l => ( l :: ls, rs), r => ( ls, r :: rs ) ) 
       }
res5: (List[Int], List[String]) = (List(1, 2, 3),List(A, B, C))

您可以使用来自 MonadPlus (scalaz) 或 MonadCombine (cats) 的 separate :

import scala.util.{Either, Left, Right}

import scalaz.std.list._
import scalaz.std.either._
import scalaz.syntax.monadPlus._

val l: List[Either[String, Int]] = List(Right(1), Left("error"), Right(2))
l.separate  
// (List[String], List[Int]) = (List(error),List(1, 2))