41

我有一些类似下面的代码,我有一个列表,我想把它变成列表中的一个……特别是(在这种情况下),如果列表中有任何左,那么我返回a 列表的左侧,否则我返回权限列表的右侧。

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总是让我觉得我一定错过了什么。

有没有更惯用的方法来做到这一点?

4

9 回答 9

28
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)
}
于 2011-06-27T09:27:09.380 回答
12

从 开始Scala 2.13,大多数集合现在都提供了一种partitionMap方法,该方法基于返回Right或的函数来划分元素Left

在我们的例子中,我们甚至不需要将输入转换为RightLeft定义分区的函数,因为我们已经有了Rights 和Lefts。因此,一个简单的使用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))
于 2018-10-06T08:35:10.667 回答
10

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)
于 2015-04-25T13:17:03.273 回答
6
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)
于 2011-06-27T07:49:15.333 回答
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))
于 2011-06-27T10:30:37.377 回答
2

我有点不想要任何因果报应,因为它是Chris 的答案和 Viktor 从这里的答案的合并.. 但这里有一个替代方案:

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), l@Left(a)) => (as += a, bs)
    case ((as, bs), r@Right(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))
于 2012-12-14T09:06:29.387 回答
2

如果您想拥有更通用且功能更强大的东西,那么Validated来自猫库的类型可能是您想要的。类似的东西Either可以聚合错误。结合NonEmptyList它可以非常强大。

http://typelevel.org/cats/datatypes/validated.html

于 2017-02-06T13:58:26.470 回答
1

不是更优雅的方式吗?

  def flatten[E,A](es: List[Either[E,A]]): Either[E,List[A]] = {
    @tailrec
    def go(tail: List[Either[E,A]], acc: List[A]): Either[E,List[A]] = tail match {
      case Nil  => Right(acc)
      case h::t => h match {
        case Left(e)  => Left(e)
        case Right(a) => go(t, a :: acc)
      }
    }
    go(es, Nil) map { _ reverse }
  }
  • 尾递归
  • 一次通过,假设a :: acc是子弹快
  • 但最终还是逆转
  • partitionMap, 可能更快,因为基于 builder 的内部实现
  • 但这一个是懒惰的。你会立即离开。
于 2020-02-25T18:32:36.053 回答
0

分别提取左和右:

val data: List[Either[String, Int]] = List(
  Right(1), 
  Left("Error #1"), 
  Right(42), 
  Left("Error #2")
)


val numbers: List[Int] = data.collect { case Right(value) => value }
val errors: List[String] = data.collect { case Left(error) => error }


println(numbers) // List(1, 42)
println(errors)  // List(Error #1, Error #2)
于 2020-06-12T15:07:30.577 回答