如何将Seq [A [B,B]]减少到[A,Seq [B]]

zig*_*tar 48 functional-programming scala scalaz

鉴于eithers的序列Seq[Either[String,A]]Left作为一个错误消息.我想获得一个Either[String,Seq[A]]在那里我得到了Right(这将是一个Seq[A]),如果序列的所有元素Right.如果至少有一个Left(错误消息),我想获取第一条错误消息或所有错误消息的串联.

当然你可以发布scalaz代码,但我也对不使用它的代码感兴趣.

编辑

我改变了标题,最初要求Either[Seq[A],Seq[B]]反映信息的正文.

Ben*_*mes 28

编辑:我错过了你的问题的标题要求Either[Seq[A],Seq[B]],但我确实读过"我想获取第一条错误消息或所有错误消息的串联",这将给你前者:

def sequence[A, B](s: Seq[Either[A, B]]): Either[A, Seq[B]] =
  s.foldRight(Right(Nil): Either[A, List[B]]) {
    (e, acc) => for (xs <- acc.right; x <- e.right) yield x :: xs
  }

scala> sequence(List(Right(1), Right(2), Right(3)))
res2: Either[Nothing,Seq[Int]] = Right(List(1, 2, 3))

scala> sequence(List(Right(1), Left("error"), Right(3)))
res3: Either[java.lang.String,Seq[Int]] = Left(error)
Run Code Online (Sandbox Code Playgroud)

使用Scalaz:

val xs: List[Either[String, Int]] = List(Right(1), Right(2), Right(3))

scala> xs.sequenceU
res0:  scala.util.Either[String,List[Int]] = Right(List(1, 2, 3))
Run Code Online (Sandbox Code Playgroud)


Kev*_*ght 16

给出一个起始序列xs,这是我的看法:

xs collectFirst { case x@Left(_) => x } getOrElse
  Right(xs collect {case Right(x) => x})
Run Code Online (Sandbox Code Playgroud)

这是对问题正文的回答,只获得第一个错误Either[String,Seq[A]].这显然不是标题中问题的有效答案


要返回所有错误:

val lefts = xs collect {case Left(x) => x }
def rights = xs collect {case Right(x) => x}
if(lefts.isEmpty) Right(rights) else Left(lefts)
Run Code Online (Sandbox Code Playgroud)

请注意,它rights被定义为一种方法,因此只有在必要时才会根据需要进行评估

  • @Nicolas:是的,但这是一个公平交易,清晰度与过早优化.很难看到这种性质的问题达到足够大的尺寸,以至于性能损失将是显而易见的. (6认同)

Ton*_*ris 10

这是scalaz代码:

_.sequence


Nic*_*las 9

它应该工作:

def unfoldRes[A](x: Seq[Either[String, A]]) = x partition {_.isLeft} match {
  case (Seq(), r) => Right(r map {_.right.get})
  case (l, _) => Left(l map {_.left.get} mkString "\n")
}
Run Code Online (Sandbox Code Playgroud)

你左右分开你的结果,如果左边是空的,建立一个右边,否则,建立一个左边.


Xav*_*hot 6

从 开始Scala 2.13,大多数集合都提供了一种partitionMap方法,该方法根据将项目映射到 或 的函数来对元素进行Right分区Left

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

然后,只需根据是否有左项来匹配左项和右项的结果分区元组即可:

eithers.partitionMap(identity) match {
  case (Nil, rights)       => Right(rights)
  case (firstLeft :: _, _) => Left(firstLeft)
}

// * val eithers: List[Either[String, Int]] = List(Right(1), Right(2), Right(3))
//         => Either[String,List[Int]] = Right(List(1, 2, 3))
// * val eithers: List[Either[String, Int]] = List(Right(1), Left("error1"), Right(3), Left("error2"))
//         => Either[String,List[Int]] = Left("error1")
Run Code Online (Sandbox Code Playgroud)

中间步骤( partitionMap)的详细信息:

List(Right(1), Left("error1"), Right(3), Left("error2")).partitionMap(identity)
// => (List[String], List[Int]) = (List("error1", "error2"), List(1, 3))
Run Code Online (Sandbox Code Playgroud)