如何拆分List[Either[A, B]]?

29

我想把一个 List[Either[A, B]] 分成两个列表。

有更好的方法吗?

def lefts[A, B](eithers : List[Either[A, B]]) : List[A] = eithers.collect { case Left(l) => l}
def rights[A, B](eithers : List[Either[A, B]]) : List[B] = eithers.collect { case Right(r) => r}
9个回答

37
从 Scala 2.13 版本开始,大多数集合现在都提供了一个 partitionMap 方法,该方法根据返回 RightLeft 的函数对元素进行分区。
在我们的情况下,我们甚至不需要将输入转换为 RightLeft 的函数来定义分区,因为我们已经有了 RightLeft。因此,只需要简单地使用 identity
val (lefts, rights) = List(Right(2), Left("a"), Left("b")).partitionMap(identity)
// lefts: List[String] = List(a, b)
// rights: List[Int] = List(2)

26

不确定这是否更加整洁,但:

scala> def splitEitherList[A,B](el: List[Either[A,B]]) = {
         val (lefts, rights) = el.partition(_.isLeft)
         (lefts.map(_.left.get), rights.map(_.right.get))
       }
splitEitherList: [A, B](el: List[Either[A,B]])(List[A], List[B])

scala> val el : List[Either[Int, String]] = List(Left(1), Right("Success"), Left(42))
el: List[Either[Int,String]] = List(Left(1), Right(Success), Left(42))

scala> val (leftValues, rightValues) = splitEitherList(el)
leftValues: List[Int] = List(1, 42)
rightValues: List[String] = List("Success")

3
Edit: (lefts.map(.left.get), rights.map(.right.get)) 可以翻译为 (lefts.flatMap(.left.toOption), rights.flatMap(.right.toOption))。在新的版本中,使用 flatMap 和 toOption 方法代替了 get 方法,以更加清晰和安全地获取左右两边的值。这个修改保留了原始代码的意思和功能,只是使其更容易理解和维护。 - Lombric

19

如果scalaz是您的依赖之一,我建议直接使用separate

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

val el : List[Either[Int, String]] = List(Left(1), Right("Success"), Left(42))

scala> val (lefts, rights) = el.separate
lefts: List[Int] = List(1, 42)
rights: List[String] = List(Success)

3
能否提供一个更完整的答案,包括所有必需的导入?以及具体使用的scalaz版本是什么? - noahlz
1
libraryDependencies += "org.scalaz" %% "scalaz-core" % "7.2.2",以及 import scalaz.Scalaz._。更多信息请参见:http://stackoverflow.com/questions/36878459/how-to-turn-a-list-of-eithers-to-a-either-of-lists-using-scalaz-monadplus-separa/36883766#36883766 - David Portabella
12
如果你使用 cats,你也有 separate!http://typelevel.org/cats/api/cats/syntax/SeparateOps.html#separate(implicitF:cats.MonadCombine[F],implicitG:cats.Bifoldable[G]):(F[A],F[B]) - Gabriele Petronella

9
一种紧凑但不高效的解决方案:
val lefts = list.flatMap(_.left.toOption)
val rights = list.flatMap(_.right.toOption)

9

您可以使用以下方法:

val (lefts, rights) = eithers.foldRight((List[Int](), List[String]()))((e, p) => e.fold(l => (l :: p._1, p._2), r => (p._1, r :: p._2)))

1
如果您打算像Marth的回答那样抽象功能,那么使用roterl的解决方案可能更有意义。
def splitEitherList[A,B](el: List[Either[A,B]]): (List[A], List[B]) =
  (el :\ (List[A](), List[B]()))((e, p) =>
    e.fold(l => (l :: p._1, p._2), r => (p._1, r :: p._2)))

val x = List(Left(1), Right(3), Left(2), Left(4), Right(8))
splitEitherList(x) // (List(1, 2, 4), List(3, 8))

这种方法可以获得更多的功能点,而且可能更高效,因为它利用了正确的折叠来一次性创建列表。但是,如果您在现场执行此操作和/或发现折叠难以阅读,则可以使用其他方法。
el.partition(_.isLeft) match { case (lefts, rights) =>
  (lefts.map(_.left.get), rights.map(_.right.get)) }

1

好的,如果这不必是一行代码,那么它可以变得非常简单。

def split[A,B](eithers : List[Either[A, B]]):(List[A],List[B]) = {
  val lefts = scala.collection.mutable.ListBuffer[A]()
  val rights = scala.collection.mutable.ListBuffer[B]()
  eithers.map {
    case Left(l) => lefts += l
    case Right(r) => rights += r
  }
  (lefts.toList, rights.toList)
}

但说实话,我更喜欢 Marth 的回答 :)

0
一个有点功能的 Seq 解决方案。
def partition[A, B](seq: Seq[Either[A, B]]): (Seq[A], Seq[B]) = {
  seq.foldLeft[(Seq[A], Seq[B])]((Nil, Nil)) { case ((ls, rs), next) =>
    next match {
      case Left(l) => (ls :+ l, rs)
      case Right(r) => (ls, rs :+ r)
    }
  }
}

0

类似于 Scalaz 对于 Cats,你也可以使用 separate

import cats.implicits._
val items = List(Right(1), Left("error"), Right(2), Right(3), Left("another error"))

val groupedItems: (List[String], List[Int]) = items.separate

//(List(error, another error),List(1, 2, 3))

我错过了在 https://dev59.com/6F8d5IYBdhLWcg3wpzn4#72651774 评论中已经有这个答案的事实。 - Llewellyn

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接