Как разбить список [Либо [A, B]]

Я хочу разделить List[Either[A, B]] на два списка.

Есть ли лучший способ?

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}

Ответы

Ответ 1

Не уверен, что это действительно намного опрятно, но:

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")

Ответ 2

Если scalaz является одной из ваших зависимостей, я бы просто использовал 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)

Ответ 3

Вы можете сделать это с помощью

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)))

Ответ 4

Компактный, но не решающий ЦП решение:

val lefts = list.flatMap(_.left.toOption)
val rights = list.flatMap(_.right.toOption)

Ответ 5

Начиная Scala 2.13, большинство коллекций теперь снабжены Either[A1,A2]):(CC[A1],CC[A2]) rel="nofollow noreferrer"> partitionMap метод, который делит элементы на основе функции, которая возвращает либо Right или Left.

В нашем случае нам даже не нужна функция, которая преобразует наш ввод в Right или Left чтобы определить разбиение, поскольку у нас уже есть Right и Left s. Таким образом, простое использование identity:

val (lefts, rights) = List(Right(2), Left("a"), Left("b")).partitionMap(identity)
// lefts: List[String] = List(a, b)
// rights: List[Int] = List(2)

Ответ 6

Ну, в случае, если он не должен быть однострочным... тогда это может быть без проблем.

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)
}

Но, честно говоря, я бы предпочел ответить Marth:)

Ответ 7

Несколько функциональное решение для 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)
    }
  }
}

Ответ 8

Если вы собираетесь отвлечься от абстрагирования функциональности, как в ответе Марта, то, возможно, на самом деле имеет смысл использовать решение 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))

Этот способ присуждает больше функциональных очков брауни, но также может быть более производительным, так как он использует правильную складку для создания списков за один проход

Но если вы делаете это на лету и/или находите складки трудными для чтения, то непременно

el.partition(_.isLeft) match { case (lefts, rights) =>
  (lefts.map(_.left.get), rights.map(_.right.get)) }