scala 将任何一个列表转换为一个列表的最佳方法?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/6489584/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-22 03:08:28  来源:igfitidea点击:

Best way to turn a Lists of Eithers into an Either of Lists?

scalaeither

提问by Eric Bowman - abstracto -

I have some code like the below, where I have a list of Eithers, and I want to turn it into an Either of Lists ... in particular (in this case), if there are any Lefts in the list, then I return a Left of the list of them, otherwise I return a Right of the list of the rights.

我有一些像下面这样的代码,我有一个列表,我想把它变成一个列表中的一个......特别是(在这种情况下),如果列表中有任何左边,那么我返回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)
}

Calling getalways makes me feel like I must be missing something.

打电话get总是让我觉得我一定错过了什么。

Is there a more idiomatic way to do this?

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

采纳答案by Viktor Klang

data.partition(_.isLeft) match {                            
  case (Nil,  ints) => Right(for(Right(i) <- ints) yield i)        
  case (strings, _) => Left(for(Left(s) <- strings) yield s)
}

For one pass:

一次通过:

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

回答by Przemek

Solution from Functional Programming in Scala book.

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)

回答by Landei

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)

回答by Xavier Guihot

Starting Scala 2.13, most collections are now provided with a partitionMapmethod which partitions elements based on a function which returns either Rightor Left.

首先Scala 2.13,现在大多数集合都提供了一种partitionMap方法,该方法根据返回Right或的函数对元素进行分区Left

In our case, we don't even need a function that transforms our input into Rightor Leftto define the partitioning since we already have Rights and Lefts. Thus a simple use of identity!

在我们的例子中,我们甚至不需要一个函数来将我们的输入转换为RightLeft定义分区,因为我们已经有了Rights 和Lefts。因此一个简单的使用identity

Then it's a simple matter of matching the resulting partitioned tuple of lefts and rights based on whether or not there are lefts:

然后根据是否存在左和右来匹配生成的左和右分区元组是一个简单的问题:

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

For the understanding of partitionMaphere is the result of the intermediate step:

为了理解partitionMap这里是中间步骤的结果:

List(Right(3), Left("error x"), Right(7)).partitionMap(identity)
// (List[String], List[Int]) = (List(error x), List(3, 7))

回答by oxbow_lakes

You can write a generalized version of splitas follows:

您可以编写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)
}

Such that:

这样:

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

回答by Dale Wijnand

I kind of don't want any karma for this as it's a merge of Chris's answerand Viktor's from here.. but here's an alternative:

我有点不想要任何因果报应,因为它是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())
  }

Example:

例子:

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

回答by bsmk

If you want to have something more general and also functional then Validatedfrom cats library is the type you might want. It is something like Eitherthat can aggregate Errors. And in combination with NonEmptyListit can be really powerful.

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

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

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

回答by Alexey Rykhalskiy

Isn't a more elegant way?

不是更优雅的方式吗?

  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 }
  }
  • tail recursion
  • one pass, assuming the a :: accis a bullet-fast
  • but still, reverse in the end
  • partitionMap, is probably faster, because of internal implementation based on builder
  • but this one is the lasy. You will get Left immediately.
  • 尾递归
  • 一次通过,假设a :: acc是快速
  • 但最后还是反转
  • partitionMap, 可能更快,因为基于构建器的内部实现
  • 但这是一个懒惰的。你会立即离开。