给定Seq[Either[String,A]]
序列,其中Left
为错误消息。如果序列的所有元素都是Right
,我想获得Either[String,Seq[A]]
,其中我得到Right
(这将是Seq[A]
)。如果至少有一个Left
(一个错误消息),我想获得第一个错误消息或所有错误消息的连接。
当然你可以发布cat或scalaz代码,但我也对不使用它的代码感兴趣。
编辑
我已经改变了标题,它最初要求一个Either[Seq[A],Seq[B]]
来反映消息的主体
编辑:我错过了你的问题的标题要求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)
使用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))
给定起始序列xs
,以下是我的看法:
xs collectFirst { case x@Left(_) => x } getOrElse
Right(xs collect {case Right(x) => x})
这是对问题主体的回答,只获得第一个错误作为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)
注意,rights
被定义为一个方法,所以它只会在需要时被计算,如果有必要的话
应该可以:
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")
}
将结果分为left和right,如果left为空,则创建right,否则创建left。
scalaz代码:
_.sequence
从Scala 2.13
开始,大多数集合都提供了一个partitionMap
方法,该方法基于将项映射到Right
或Left
的函数来划分元素。
在本例中,我们甚至不需要将输入转换为Right
或Left
的函数来定义分区,因为我们已经有了Right
s和Left
s。这就是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")
中间步骤(partitionMap
)细节:
List(Right(1), Left("error1"), Right(3), Left("error2")).partitionMap(identity)
// => (List[String], List[Int]) = (List("error1", "error2"), List(1, 3))
以Kevin的解决方案为基础,从Haskell的Either类型中借鉴一点,您可以创建一个方法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))
}
并使用它来构建您的解决方案
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)
}
我不习惯使用两者-这是我的方法;也许有更优雅的解决方案:
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)
看起来有点滑稽,但l
本身是一个Either [a, B],而不是一个Either [a, Seq[B]],需要重新包装。
我的回答类似于@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))