Scala:根据类型进行过滤

14 functional-programming scala class list filter

我正在学习Scala,因为它很符合我的需求,但我发现很难优雅地构造代码.我处于一种情况,我有一个List x并且想要创建两个Lists:一个包含所有元素,SomeClass一个包含所有不属于的元素SomeClass.

val a = x collect {case y:SomeClass => y}
val b = x filterNot {_.isInstanceOf[SomeClass]}
Run Code Online (Sandbox Code Playgroud)

现在我的代码看起来像那样.然而,它不是非常有效,因为它迭代x两次,代码似乎有点hackish.是否有更好(更优雅)的做事方式?

可以假设SomeClass没有子类.

mkn*_*ssl 8

EDITED

虽然可以使用plain partition,但它会丢失collect问题中保留的类型信息.

可以定义一个partition方法的变体,该变体接受一个函数,该函数使用以下方法返回两种类型之一的值Either:

import collection.mutable.ListBuffer

def partition[X,A,B](xs: List[X])(f: X=>Either[A,B]): (List[A],List[B]) = {
  val as = new ListBuffer[A]
  val bs = new ListBuffer[B]
  for (x <- xs) {
    f(x) match {
      case Left(a) => as += a
      case Right(b) => bs += b
    }
  }
  (as.toList, bs.toList)
}
Run Code Online (Sandbox Code Playgroud)

然后保留类型:

scala> partition(List(1,"two", 3)) {
  case i: Int => Left(i)
  case x => Right(x)
}

res5: (List[Int], List[Any]) = (List(1, 3),List(two))
Run Code Online (Sandbox Code Playgroud)

当然,可以使用构建器和所有改进的集合来改进解决方案:).

为了完整性我使用简单的旧答案partition:

val (a,b) = x partition { _.isInstanceOf[SomeClass] }
Run Code Online (Sandbox Code Playgroud)

例如:

scala> val x = List(1,2, "three")
x: List[Any] = List(1, 2, three)

scala> val (a,b) = x partition { _.isInstanceOf[Int] }
a: List[Any] = List(1, 2)
b: List[Any] = List(three)
Run Code Online (Sandbox Code Playgroud)


jsu*_*eth 5

只想扩展mkneissl的答案,使用"更通用"的版本,该版本应该适用于库中的许多不同集合:

scala> import collection._
import collection._

scala> import generic.CanBuildFrom
import generic.CanBuildFrom

scala> def partition[X,A,B,CC[X] <: Traversable[X], To, To2](xs : CC[X])(f : X => Either[A,B])(
     |   implicit cbf1 : CanBuildFrom[CC[X],A,To], cbf2 : CanBuildFrom[CC[X],B,To2]) : (To, To2) = {
     |   val left = cbf1()
     |   val right = cbf2()
     |   xs.foreach(f(_).fold(left +=, right +=))
     |   (left.result(), right.result())
     | }
partition: [X,A,B,CC[X] <: Traversable[X],To,To2](xs: CC[X])(f: (X) => Either[A,B])(implicit cbf1: scala.collection.generic.CanBuildFrom[CC[X],A,To],implicit cbf2: scala.collection.generic.CanBuildFrom[CC[X],B,To2])(To, To2)

scala> partition(List(1,"two", 3)) {                                                                
     |   case i: Int => Left(i)                                                                     
     |   case x => Right(x)                                                                         
     | }
res5: (List[Int], List[Any]) = (List(1, 3),List(two))

scala> partition(Vector(1,"two", 3)) {
     |   case i: Int => Left(i)       
     |   case x => Right(x)           
     | }
res6: (scala.collection.immutable.Vector[Int], scala.collection.immutable.Vector[Any]) = (Vector(1, 3),Vector(two))
Run Code Online (Sandbox Code Playgroud)

只需注意一点:分区方法类似,但我们需要捕获几种类型:

X - >集合中项目的原始类型.

A - >左侧分区中的项目类型

B - >右分区中的项目类型

CC - >集合的"特定"类型(Vector,List,Seq等)这必须是更高级的.我们也许可以解决一些类型推断问题(见阿德里安的响应位置:http://suereth.blogspot.com/2010/06/preserving-types-and-differing-subclass.html),但我感觉懒惰;)

To - >左侧的完整集合类型

To2 - >右侧的完整集合类型

最后,搞笑的"CanBuildFrom"隐含的外观和数据是什么让我们来构建特定的类型,如列表或Vector,一般.它们内置于所有核心库集合中.

具有讽刺意味的是,CanBuildFrom魔术的全部原因是正确处理BitSet.因为我需要更高的CC,我们在使用分区时会得到这个有趣的错误消息:

scala> partition(BitSet(1,2, 3)) {    
     |   case i if i % 2 == 0  => Left(i)
     |   case i if i % 2 == 1 => Right("ODD")
     | }
<console>:11: error: type mismatch;
 found   : scala.collection.BitSet
 required: ?CC[ ?X ]
Note that implicit conversions are not applicable because they are ambiguous:
 both method any2ArrowAssoc in object Predef of type [A](x: A)ArrowAssoc[A]
 and method any2Ensuring in object Predef of type [A](x: A)Ensuring[A]
 are possible conversion functions from scala.collection.BitSet to ?CC[ ?X ]
       partition(BitSet(1,2, 3)) {
Run Code Online (Sandbox Code Playgroud)

如果需要的话我会把这个开放给有人来修理!我会看看是否可以在更多游戏之后为您提供适用于BitSet的解决方案.