我正在学习Scala,因为它很符合我的需求,但我发现很难优雅地构建代码。我的情况是List
x
并且想要创建两个List
:一个包含SomeClass
的所有元素,另一个包含所有元素不属于SomeClass
。
val a = x collect {case y:SomeClass => y}
val b = x filterNot {_.isInstanceOf[SomeClass]}
现在我的代码看起来像那样。然而,它不是非常有效,因为它迭代x
两次,代码似乎有点hackish。是否有更好(更优雅)的做事方式?
可以假设SomeClass
没有子类。
答案 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
方法,该方法根据返回Right
或Left
的函数对元素进行分区。
这使我们可以模式匹配给定类型(此处为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))