Как уменьшить Seq [Либо [A, B]] до Либо [A, Seq [B]]?
Учитывая последовательность eithers Seq[Either[String,A]]
с Left
, является сообщением об ошибке. Я хочу получить Either[String,Seq[A]]
, где я получаю a Right
(который будет Seq[A]
), если все элементы последовательности Right
. Если есть хотя бы один Left
(сообщение об ошибке), я бы хотел получить первое сообщение об ошибке или конкатенацию всех сообщений об ошибках.
Конечно, вы можете опубликовать код scalaz, но меня тоже интересует код, который его не использует.
Изменить
Я изменил заголовок, изначально запрошенный Either[Seq[A],Seq[B]]
, чтобы отразить тело сообщения.
Ответы
Ответ 1
Изменить: я пропустил, что название вашего вопроса задано для 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))
Ответ 2
Учитывая начальную последовательность xs
, здесь я беру:
xs collectFirst { case [email protected](_) => 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
определяется как метод, поэтому он будет оцениваться только по требованию, если необходимо
Ответ 3
Он должен работать:
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")
}
Вы разделите свой результат налево и вправо, если левый пуст, постройте справа, иначе постройте левый.
Ответ 4
Вот код scalaz:
_.sequence
Ответ 5
Основываясь на решении Кевина и немного украя у 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))
}
И используйте это для создания своего решения
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
Начиная с Scala 2.13
, большинство коллекций снабжено методом Either[A1,A2]):(CC[A1],CC[A2]) rel="nofollow noreferrer"> partitionMap
который разделяет элементы на основе функции, которая отображает элементы в Right
или Left
сторону.
В нашем случае нам даже не нужна функция, которая преобразует наши входные данные в Right
или Left
для определения разделения, поскольку у нас уже есть Right
и 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))
Ответ 7
Я не привык использовать Либо - вот мой подход; возможно, есть более элегантные решения:
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
сам является Либо [A, B], а не Aither [A, Seq [B]] и нуждается в повторной обработке.
Ответ 8
Мой ответ похож на @Garrett Rowe's: Но он использует 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))