将“任一列表”转换为“任一列表”的最佳方法?

发布于 2024-11-17 04:53:34 字数 406 浏览 3 评论 0原文

我有一些如下所示的代码,其中有一个列表,我想将其转换为列表中的一个。特别是(在本例中),如果列表中有任何 Left,则返回它们列表的 Left,否则返回权利列表的 Right。

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

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

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.

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 get always makes me feel like I must be missing something.

Is there a more idiomatic way to do this?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(9

聆听风音 2024-11-24 04:53:34
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)
}
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)
}
孤芳又自赏 2024-11-24 04:53:34

从 Scala 2.13 开始,大多数集合现在都提供了 partitionMap 方法,根据返回 RightLeft 的函数对元素进行分区。

在我们的例子中,我们甚至不需要将输入转换为 RightLeft 的函数来定义分区,因为我们已经有了 Right s 和 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))

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

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

Starting in Scala 2.13, most collections are now provided with a partitionMap method which partitions elements based on a function returning either Right or Left.

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

Then it's just a matter of matching the resulting partitioned tuple of lefts and rights based on whether or not there are any 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 partitionMap here is the result of the intermediate step:

List(Right(3), Left("error x"), Right(7)).partitionMap(identity)
// (List[String], List[Int]) = (List(error x), List(3, 7))
硪扪都還晓 2024-11-24 04:53:34

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)

Solution from Functional Programming in Scala book.

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)
暮年慕年 2024-11-24 04:53:34
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)
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)
看春风乍起 2024-11-24 04:53:34

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

You can write a generalized version of split as follows:

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))
小矜持 2024-11-24 04:53:34

我有点不希望有任何业力,因为它是 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))

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

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))
南城旧梦 2024-11-24 04:53:34

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

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

If you want to have something more general and also functional then Validated from cats library is the type you might want. It is something like Either that can aggregate Errors. And in combination with NonEmptyList it can be really powerful.

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

才能让你更想念 2024-11-24 04:53:34

难道不是更优雅的方式吗?

  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 可能更快,因为基于构建器的内部实现
  • 但这个是懒惰的。您将立即离开。

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 :: acc is 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.
述情 2024-11-24 04:53:34

分别提取左和右:

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)

To extract Lefts and Rights separately:

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)
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文