问题
Given a sequence of eithers Seq[Either[String,A]]
with Left
being an error message. 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
. 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.
Of course you can post scalaz code but I'm also interested in code not using it.
Edit
I've changed the title, which originally asked for an Either[Seq[A],Seq[B]]
to reflect the body of the message.
回答1:
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:
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:
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))
回答2:
Given a starting sequence xs
, here's my take:
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]]
. 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
回答3:
Here is the scalaz code:
_.sequence
回答4:
It should work:
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")
}
You split your result in left and right, if left is empty, build a Right, otherwise, build a left.
回答5:
Building on Kevin's solution, and stealing a bit from Haskell's Either type, you can create a method partitionEithers like so:
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)
}
回答6:
Starting 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
.
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. Thus a simple use of identity
!
Then it's a simple 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
):
List(Right(1), Left("error1"), Right(3), Left("error2")).partitionMap(identity)
// => (List[String], List[Int]) = (List("error1", "error2"), List(1, 3))
回答7:
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.
回答8:
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?).
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))
来源:https://stackoverflow.com/questions/7230999/how-to-reduce-seqeithera-b-to-eithera-seqb