简体   繁体   English

如何将 Seq[Either[A,B]] 减少为 [A,Seq[B]]?

[英]How to reduce Seq[Either[A,B]] to Either[A,Seq[B]]?

Given a sequence of eithers Seq[Either[String,A]] with Left being an error message.给定一个序列的任一个Seq[Either[String,A]]其中Left是一条错误消息。 I want to obtain an Either[String,Seq[A]] where I get a Right (which will be a Seq[A] ), if all elements of the sequence are Right .如果序列的所有元素都是Right ,我想获得一个Either[String,Seq[A]]在那里我得到一个Right (这将是一个Seq[A] )。 If there is at least one Left (an error message), I'd like to obtain the first error message or a concatenation of all error messages.如果至少有一个Left (错误消息),我想获取第一条错误消息或所有错误消息的串联。

Of course you can post scalaz code but I'm also interested in code not using it.当然,您可以发布 scalaz 代码,但我也对不使用它的代码感兴趣。

Edit编辑

I've changed the title, which originally asked for an Either[Seq[A],Seq[B]] to reflect the body of the message.我已经更改了标题,它最初要求使用Either[Seq[A],Seq[B]]来反映消息的正文。

Edit: I missed that the title of your question asked for Either[Seq[A],Seq[B]] , but I did read "I'd like to obtain the first error message or a concatenation of all error messages", and this would give you the former:编辑:我错过了您的问题的标题要求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)

Using Scalaz:使用 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))

Given a starting sequence xs , here's my take:给定一个起始序列xs ,这是我的看法:

xs collectFirst { case x@Left(_) => x } getOrElse
  Right(xs collect {case Right(x) => x})

This being in answer to the body of the question, obtaining only the first error as an Either[String,Seq[A]] .这是对问题正文的回答,仅获得第一个错误作为Either[String,Seq[A]] It's obviously not a valid answer to the question in the title这显然不是标题中问题的有效答案


To return all errors:返回所有错误:

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

Note that rights is defined as a method, so it'll only be evaluated on demand, if necessary请注意, rights被定义为一种方法,因此只有在需要时才会对其进行评估

It should work:它应该工作:

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

You split your result in left and right, if left is empty, build a Right, otherwise, build a left.您将结果分为左右两部分,如果左侧为空,则构建右侧,否则构建左侧。

这是scalaz代码:

_.sequence

Starting in Scala 2.13 , most collections are provided with a partitionMap method which partitions elements based on a function which maps items to either Right or Left .Scala 2.13开始,大多数集合都提供了partitionMap方法,该方法基于将项目映射到RightLeft的函数对元素进行分区。

In our case, we don't even need a function that transforms our input into Right or Left to define the partitioning since we already have Right s and Left s.在我们的例子中,我们甚至不需要将输入转换为RightLeft的函数来定义分区,因为我们已经有了Right s 和Left s。 Thus a simple use of identity !因此一个简单的使用identity

Then it's just a matter of matching the resulting partitioned tuple of lefts and rights based on whether or not there are lefts:然后它只是根据是否有左匹配生成的左和右分区元组的问题:

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

Details of the intermediate step ( partitionMap ):中间步骤( partitionMap )的详细信息:

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

Building on Kevin's solution, and stealing a bit from Haskell's Either type, you can create a method partitionEithers like so:基于 Kevin 的解决方案,并从 Haskell 的任何类型中窃取一点,您可以创建一个方法 partitionEithers,如下所示:

def partitionEithers[A, B](es: Seq[Either[A, B]]): (Seq[A], Seq[B]) =
  es.foldRight (Seq.empty[A], Seq.empty[B]) { case (e, (as, bs)) =>
    e.fold (a => (a +: as, bs), b => (as, b +: bs))
  }

And use that to build your solution并使用它来构建您的解决方案

def unroll[A, B](es: Seq[Either[A, B]]): Either[Seq[A], Seq[B]] = {
  val (as, bs) = partitionEithers(es)
  if (!as.isEmpty) Left(as) else Right(bs)
}

My answer is similar to @Garrett Rowe's: But it uses foldLeft (Also see: Why foldRight and reduceRight are NOT tail recursive? ) and prepends to Seq rather than appending to Seq (See: Why is appending to a list bad? ).我的答案类似于@Garrett Rowe 的:但它使用 foldLeft(另请参阅: 为什么 foldRight 和 reduceRight 不是尾递归?并附加到 Seq 而不是附加到 Seq(请参阅: 为什么附加到列表不好? )。

scala> :paste
// Entering paste mode (ctrl-D to finish)

def partitionEitherSeq[A,B](eitherSeq: Seq[Either[A,B]]): (Seq[A], Seq[B]) =
  eitherSeq.foldLeft(Seq.empty[A], Seq.empty[B]) { (acc, next) =>
  val (lefts, rights) = acc
  next.fold(error => (lefts :+ error, rights), result => (lefts, rights :+ result))
}

// Exiting paste mode, now interpreting.

partitionEitherSeq: [A, B](eitherSeq: Seq[Either[A,B]])(Seq[A], Seq[B])

scala> partitionEitherSeq(Seq(Right("Result1"), Left("Error1"), Right("Result2"), Right("Result3"), Left("Error2")))
res0: (Seq[java.lang.String], Seq[java.lang.String]) = (List(Error1, Error2),List(Result1, Result2, Result3))

I'm not used to use Either - here is my approach;我不习惯使用任何一种 - 这是我的方法; maybe there are more elegant solutions:也许有更优雅的解决方案:

def condense [A] (sesa: Seq [Either [String, A]]): Either [String, Seq [A]] = {
  val l = sesa.find (e => e.isLeft)
  if (l == None) Right (sesa.map (e => e.right.get)) 
  else Left (l.get.left.get)
}

condense (List (Right (3), Right (4), Left ("missing"), Right (2)))
// Either[String,Seq[Int]] = Left(missing)
condense (List (Right (3), Right (4), Right (1), Right (2)))
// Either[String,Seq[Int]] = Right(List(3, 4, 1, 2))

Left (l.get.left.get) looks a bit funny, but l itself is a Either [A, B], not an Either [A, Seq[B]], and needs rewrapping. Left (l.get.left.get)看起来有点滑稽,但l本身是一个或 [A, B],而不是一个 [A, Seq[B]],需要重新包装。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM