scala - Cómo dividir una lista[O bien
either (8)
Quiero dividir una List[Either[A, B]]
en dos listas.
¿Hay alguna manera mejor?
def lefts[A, B](eithers : List[Either[A, B]]) : List[A] = eithers.collect { case Left(l) => l}
def rights[A, B](eithers : List[Either[A, B]]) : List[B] = eithers.collect { case Right(r) => r}
A partir de Scala 2.13
, la mayoría de las colecciones ahora cuentan con un método de partición Con elementos de partición basados en una función que devuelve Right
o Left
.
En nuestro caso, ni siquiera necesitamos una función que transforme nuestra entrada en Right
o Left
para definir la partición, ya que ya tenemos Right
e Left
. Así, un simple uso de la identity
:
val (lefts, rights) = List(Right(2), Left("a"), Left("b")).partitionWith(identity)
// lefts: List[String] = List(a, b)
// rights: List[Int] = List(2)
Bueno, en caso de que no tenga que ser de una sola línea ... entonces puede ser una obviedad.
def split[A,B](eithers : List[Either[A, B]]):(List[A],List[B]) = {
val lefts = scala.collection.mutable.ListBuffer[A]()
val rights = scala.collection.mutable.ListBuffer[B]()
eithers.map {
case Left(l) => lefts += l
case Right(r) => rights += r
}
(lefts.toList, rights.toList)
}
Pero, para ser honesto, preferiría la respuesta de Marth :)
No estoy seguro de que esto sea realmente mucho más limpio, pero:
scala> def splitEitherList[A,B](el: List[Either[A,B]]) = {
val (lefts, rights) = el.partition(_.isLeft)
(lefts.map(_.left.get), rights.map(_.right.get))
}
splitEitherList: [A, B](el: List[Either[A,B]])(List[A], List[B])
scala> val el : List[Either[Int, String]] = List(Left(1), Right("Success"), Left(42))
el: List[Either[Int,String]] = List(Left(1), Right(Success), Left(42))
scala> val (leftValues, rightValues) = splitEitherList(el)
leftValues: List[Int] = List(1, 42)
rightValues: List[String] = List("Success")
Puedes hacerlo con:
val (lefts, rights) = eithers.foldRight((List[Int](), List[String]()))((e, p) => e.fold(l => (l :: p._1, p._2), r => (p._1, r :: p._2)))
Si scalaz
es una de sus dependencias, simplemente usaría por separate
:
import scalaz.std.list._
import scalaz.std.either._
import scalaz.syntax.monadPlus._
val el : List[Either[Int, String]] = List(Left(1), Right("Success"), Left(42))
scala> val (lefts, rights) = el.separate
lefts: List[Int] = List(1, 42)
rights: List[String] = List(Success)
Si se va a molestar en abstraer la funcionalidad, como en la respuesta de Marth, entonces puede que tenga más sentido usar la solución de roterl:
def splitEitherList[A,B](el: List[Either[A,B]]): (List[A], List[B]) =
(el :/ (List[A](), List[B]()))((e, p) =>
e.fold(l => (l :: p._1, p._2), r => (p._1, r :: p._2)))
val x = List(Left(1), Right(3), Left(2), Left(4), Right(8))
splitEitherList(x) // (List(1, 2, 4), List(3, 8))
De esta manera, se otorgan puntos brownie más funcionales, pero también puede ser más eficaz, ya que utiliza un pliegue a la derecha para crear las listas en una sola pasada.
Pero si lo haces sobre la marcha y / o encuentras pliegues difíciles de leer, entonces por todos los medios
el.partition(_.isLeft) match { case (lefts, rights) =>
(lefts.map(_.left.get), rights.map(_.right.get)) }
Una solución algo funcional para Seq
.
def partition[A, B](seq: Seq[Either[A, B]]): (Seq[A], Seq[B]) = {
seq.foldLeft[(Seq[A], Seq[B])]((Nil, Nil)) { case ((ls, rs), next) =>
next match {
case Left(l) => (ls :+ l, rs)
case Right(r) => (ls, rs :+ r)
}
}
}
Una solución compacta, pero no eficiente para la CPU:
val lefts = list.flatMap(_.left.toOption)
val rights = list.flatMap(_.right.toOption)