Scala 将一个列表转换为两个列表之一的最佳方法?
我有一些如下的代码,我有一个列表,我想把它变成一个列表。。。特别是(在本例中),如果列表中有任何左撇子,那么我将返回它们列表中的左撇子,否则将返回右撇子Scala 将一个列表转换为两个列表之一的最佳方法?,scala,either,Scala,Either,我有一些如下的代码,我有一个列表,我想把它变成一个列表。。。特别是(在本例中),如果列表中有任何左撇子,那么我将返回它们列表中的左撇子,否则将返回右撇子 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))
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
总是让我觉得我一定错过了什么
有更惯用的方法吗?val list=list(左(“x”)、右(2)、右(4))
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 strings=for(Left(x)data.partition(u.isLeft)匹配{
case(Nil,ints)=>Right(用于)Right(i)Left(用于)Left(s)Right(用于)Right(i)Left(用于)Left(s)您可以编写一个通用版本的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)
}
我有点不想让这件事有任何因果报应,因为这是一个和维克多的结合…但这里有一个选择:
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书中函数编程的解决方案
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)
def序列[E,A](es:List[or[E,A]]:or[E,List[A]=
横向(es)(x=>x)
def遍历[E,A,B](es:List[A])(f:A=>任一[E,B]):任一[E,List[B]=
es比赛{
案例无=>右侧(无)
案例h::t=>(f(h)map2导线测量(t)(f))()
}
def map2[EE>:E,B,C](a:one[E,a],B:one[EE,B])(f:(a,B)=>C):
[EE,C]=for{a1如果您想拥有更通用且功能更强大的内容,那么来自cats库的Validated
就是您可能想要的类型。它类似于one
,可以聚合错误。与NonEmptyList
结合使用,它可以非常强大
从Scala 2.13开始,大多数集合现在都提供了一种方法,该方法基于返回右或左的函数对元素进行分区
在我们的例子中,我们甚至不需要一个函数来将输入转换为右
或左
来定义分区,因为我们已经有了右
s和左
s。因此标识的简单用法
然后,只需根据是否存在左撇子来匹配所生成的左撇子和右撇子的分区元组:
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))
这不是更优雅的方式吗
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
,可能更快,因为它是基于构建器的内部实现
- 但这是拉西,你马上就要离开了
要分别提取左侧和右侧:
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)
我发现所描述的问题有点古怪。字符串对int的偏好似乎有点不对称。写一些类似unzipEither的东西怎么样,它返回字符串列表和int列表。这样方法就不会丢失信息,就像你的版本在混合列表中所做的那样。听起来你可能会对我们更好使用.Hmm这样的结构可以说更清楚。不过,这似乎做了更多的工作。很遗憾,你不能一次完成这项工作。如果你使用视图而不是列表作为结果类型,你可以一次完成这项工作,请参阅我的编辑。我想我必须奖励.view技巧——thx Viktor。你能解释一下为什么你认为它应该这样做吗?出于居里既然如此,为什么不创建作为
和bs
作为VAL?遍历
也可以使用foldRight
编写
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))
List(Right(3), Left("error x"), Right(7)).partitionMap(identity)
// (List[String], List[Int]) = (List(error x), List(3, 7))
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 }
}
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)