Scala:根据类型进行过滤

时间:2010-08-26 05:08:49

标签: class list scala functional-programming filter

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

val a = x collect {case y:SomeClass => y}
val b = x filterNot {_.isInstanceOf[SomeClass]}

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

可以假设SomeClass没有子类。

5 个答案:

答案 0 :(得分:8)

<强> EDITED

虽然可以使用普通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)
}

然后保留类型:

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

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

为了完整性,我的旧答案使用普通partition

val (a,b) = x partition { _.isInstanceOf[SomeClass] }

例如:

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)

答案 1 :(得分: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))

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

X - &gt;集合中项目的原始类型。

A - &gt;左侧分区中的项目类型

B - &gt;右侧分区中的项目类型

CC - &gt;集合的“特定”类型(Vector,List,Seq等)此必须更高。我们可以解决一些类型推理问题(参见Adrian在这里的回复:http://suereth.blogspot.com/2010/06/preserving-types-and-differing-subclass.html),但我感到很懒;)

To - &gt;左侧的完整收集类型

To2 - &gt;右侧的完整集合类型

最后,有趣的“CanBuildFrom”隐式参数允许我们一般地构造特定类型,如List或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)) {

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

答案 2 :(得分:4)

使用list.partition

scala> val l = List(1, 2, 3)
l: List[Int] = List(1, 2, 3)

scala> val (even, odd) = l partition { _ % 2 == 0 }
even: List[Int] = List(2)
odd: List[Int] = List(1, 3)

修改

要按类型进行分区,请使用以下方法:

def partitionByType[X, A <: X](list: List[X], typ: Class[A]): 
    Pair[List[A], List[X]] = {
    val as = new ListBuffer[A]
    val notAs = new ListBuffer[X]
    list foreach {x =>
      if (typ.isAssignableFrom(x.asInstanceOf[AnyRef].getClass)) {
        as += typ cast x 
      } else {
        notAs += x
      }
    }
    (as.toList, notAs.toList)
}

用法:

scala> val (a, b) = partitionByType(List(1, 2, "three"), classOf[java.lang.Integer])
a: List[java.lang.Integer] = List(1, 2)
b: List[Any] = List(three)

答案 3 :(得分:2)

如果列表仅包含AnyRef的子类,则由于方法getClass。你可以这样做:

scala> case class Person(name: String)                                                           
defined class Person

scala> case class Pet(name: String)                                                              
defined class Pet

scala> val l: List[AnyRef] = List(Person("Walt"), Pet("Donald"), Person("Disney"), Pet("Mickey"))
l: List[AnyRef] = List(Person(Walt), Pet(Donald), Person(Disney), Pet(Mickey))

scala> val groupedByClass = l.groupBy(e => e.getClass)
groupedByClass: scala.collection.immutable.Map[java.lang.Class[_],List[AnyRef]] = Map((class Person,List(Person(Walt), Person(Disney))), (class Pet,List(Pet(Donald), Pet(Mickey))))

scala> groupedByClass(classOf[Pet])(0).asInstanceOf[Pet]
res19: Pet = Pet(Donald)

答案 4 :(得分:0)

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

这使我们可以模式匹配给定类型(此处为Person),然后将其转换为Right,以便将其放置在结果分区元组的right列表中。其他类型可以转换为Left并在左侧进行分区:

// case class Person(name: String)
// case class Pet(name: String)
val (pets, persons) =
  List(Person("Walt"), Pet("Donald"), Person("Disney")).partitionMap {
    case person: Person => Right(person)
    case pet: Pet       => Left(pet)
  }
// persons: List[Person] = List(Person(Walt), Person(Disney))
// pets: List[Pet] = List(Pet(Donald))