Лучший способ превратить списки Эйтерса в листинг?
У меня есть код, как показано ниже, где у меня есть список Eithers, и я хочу превратить его в Либо из списков... в частности (в данном случае), если в списке есть левые, то я возвращаю Left из их списка, иначе я верну право права на список прав.
val maybe: List[Either[String, Int]] = getMaybe
val (strings, ints) = maybe.partition(_.isLeft)
strings.map(_.left.get) match {
case Nil => Right(ints.map(_.right.get))
case stringList => Left(stringList)
}
Вызов get
всегда заставляет меня чувствовать, что мне что-то не хватает.
Есть ли более идиоматический способ сделать это?
Ответы
Ответ 1
data.partition(_.isLeft) match {
case (Nil, ints) => Right(for(Right(i) <- ints) yield i)
case (strings, _) => Left(for(Left(s) <- strings) yield s)
}
За один проход:
data.partition(_.isLeft) match {
case (Nil, ints) => Right(for(Right(i) <- ints.view) yield i)
case (strings, _) => Left(for(Left(s) <- strings.view) yield s)
}
Ответ 2
Решение функционального программирования в книге Scala.
def sequence[E,A](es: List[Either[E,A]]): Either[E,List[A]] =
traverse(es)(x => x)
def traverse[E,A,B](es: List[A])(f: A => Either[E, B]): Either[E, List[B]] =
es match {
case Nil => Right(Nil)
case h::t => (f(h) map2 traverse(t)(f))(_ :: _)
}
def map2[EE >: E, B, C](a: Either[E, A], b: Either[EE, B])(f: (A, B) => C):
Either[EE, C] = for { a1 <- a; b1 <- b } yield f(a1,b1)
Ответ 3
val list = List(Left("x"),Right(2), Right(4))
val strings = for (Left(x) <- list) yield(x)
val result = if (strings.isEmpty) Right(for (Right(x) <- list) yield(x))
else Left(strings)
Ответ 4
Вы можете написать обобщенную версию split
следующим образом:
def split[X, CC[X] <: Traversable[X], A, B](l : CC[Either[A, B]])
(implicit bfa : CanBuildFrom[Nothing, A, CC[A]], bfb : CanBuildFrom[Nothing, B, CC[B]]) : (CC[A], CC[B]) = {
def as = {
val bf = bfa()
bf ++= (l collect { case Left(x) => x})
bf.result
}
def bs = {
val bf = bfb()
bf ++= (l collect { case Right(x) => x})
bf.result
}
(as, bs)
}
Таким образом:
scala> List(Left("x"),Right(2), Right(4)) : List[Either[java.lang.String,Int]]
res11: List[Either[java.lang.String,Int]] = List(Left(x), Right(2), Right(4))
scala> split(res11)
res12: (List[java.lang.String], List[Int]) = (List(x),List(2, 4))
scala> Set(Left("x"),Right(2), Right(4)) : Set[Either[java.lang.String,Int]]
res13: Set[Either[java.lang.String,Int]] = Set(Left(x), Right(2), Right(4))
scala> split(res13)
res14: (Set[java.lang.String], Set[Int]) = (Set(x),Set(2, 4))
Ответ 5
Начиная 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 (lefts, _) => Left(lefts)
}
// * List[Either[String, Int]] = List(Right(3), Left("error x"), Right(7))
// => Either[List[String],List[Int]] = Left(List(error x))
// * List[Either[String, Int]] = List(Right(3), Right(7))
// => Either[List[String],List[Int]] = Right(List(3, 7))
Для понимания partitionMap
вот результат промежуточного шага:
List(Right(3), Left("error x"), Right(7)).partitionMap(identity)
// (List[String], List[Int]) = (List(error x), List(3, 7))
Ответ 6
Я вроде бы не хочу никакой кармы для этого, так как это слияние Криса отвечает и Виктора от здесь, но здесь альтернатива:
def split[CC[X] <: Traversable[X], A, B](xs: CC[Either[A, B]])
(implicit bfa: CanBuildFrom[Nothing, A, CC[A]], bfb: CanBuildFrom[Nothing, B, CC[B]]) : (CC[A], CC[B]) =
xs.foldLeft((bfa(), bfb())) {
case ((as, bs), [email protected](a)) => (as += a, bs)
case ((as, bs), [email protected](b)) => (as, bs += b)
} match {
case (as, bs) => (as.result(), bs.result())
}
Пример:
scala> val eithers: List[Either[String, Int]] = List(Left("Hi"), Right(1))
eithers: List[Either[String,Int]] = List(Left(Hi), Right(1))
scala> split(eithers)
res0: (List[String], List[Int]) = (List(Hi),List(1))
Ответ 7
Если вы хотите иметь что-то более общее и функциональное, то Validated
из библиотеки кошек - тип, который вы, возможно, захотите. Это что-то вроде Either
, которое может агрегировать ошибки. И в сочетании с NonEmptyList
он может быть действительно мощным.
http://typelevel.org/cats/datatypes/validated.html