理解此问题的关键是要意识到,有两种不同的方法可以在集合库中构建和使用集合。一种是公共收藏夹接口及其所有好的方法。另一个是构建器,它广泛用于创建集合库,但几乎从未在其外部使用。
富集问题与集合库本身在尝试返回相同类型的集合时面临的问题完全相同。也就是说,我们要构建集合,但是在进行常规工作时,我们没有办法引用“与集合已经存在的相同类型”。所以我们需要建造者。
现在的问题是:我们从哪里得到建造者?明显的地方是来自收藏本身。 这不行。在转到通用集合时,我们已经决定要忘记集合的类型。因此,即使该集合可以返回一个生成器,该生成器将生成我们想要的类型的更多集合,它也不知道类型是什么。
相反,我们从CanBuildFrom
浮动的隐式对象中获取构建器。这些是专门为匹配输入和输出类型并为您提供适当类型的构建器而存在的。
因此,我们有两个概念上的飞跃:
- 我们没有使用标准的集合操作,而是在使用构建器。
- 我们从隐式
CanBuildFrom
s 获得这些构建器,而不是直接从我们的集合中获得。
让我们来看一个例子。
class GroupingCollection[A, C[A] <: Iterable[A]](ca: C[A]) {
import collection.generic.CanBuildFrom
def groupedWhile(p: (A,A) => Boolean)(
implicit cbfcc: CanBuildFrom[C[A],C[A],C[C[A]]], cbfc: CanBuildFrom[C[A],A,C[A]]
): C[C[A]] = {
val it = ca.iterator
val cca = cbfcc()
if (!it.hasNext) cca.result
else {
val as = cbfc()
var olda = it.next
as += olda
while (it.hasNext) {
val a = it.next
if (p(olda,a)) as += a
else { cca += as.result; as.clear; as += a }
olda = a
}
cca += as.result
}
cca.result
}
}
implicit def iterable_has_grouping[A, C[A] <: Iterable[A]](ca: C[A]) = {
new GroupingCollection[A,C](ca)
}
让我们分开。首先,为了构建集合集合,我们知道我们需要构建两种类型的集合:C[A]
对于每个组,然后C[C[A]]
将所有组收集在一起。因此,我们需要两个构建器,一个使用A
s并构建C[A]
s,另一个使用C[A]
s并构建C[C[A]]
s。看一下的类型签名CanBuildFrom
,我们看到
CanBuildFrom[-From, -Elem, +To]
这意味着CanBuildFrom想要知道我们开始的集合的类型-在我们的例子中是C[A]
,然后是生成的集合的元素和该集合的类型。因此,我们将它们作为隐式参数cbfcc
和填充cbfc
。
意识到这一点,这就是大部分工作。我们可以使用CanBuildFrom
s向我们提供构建器(您需要做的就是应用它们)。并且,一个构建器可以使用构建一个集合+=
,将其转换为最终应该使用的集合result
,然后清空自身并准备从重新开始clear
。构建器开始是空的,这解决了我们的第一个编译错误,并且由于我们使用构建器而不是递归,因此第二个错误也消失了。
最后一个小细节-除了实际工作的算法以外-隐式转换中。请注意,我们使用new GroupingCollection[A,C]
not [A,C[A]]
。这是因为类声明是用于C
一个参数的,它用A
传递给它的自身填充。因此,我们只将其传递给type C
,然后根据它创建C[A]
它。小细节,但是如果尝试另一种方法,则会出现编译时错误。
在这里,我使该方法比“相等元素”集合更为通用-相反,只要对顺序元素的测试失败,该方法就会将原始集合切开。
让我们看看我们的方法在起作用:
scala> List(1,2,2,2,3,4,4,4,5,5,1,1,1,2).groupedWhile(_ == _)
res0: List[List[Int]] = List(List(1), List(2, 2, 2), List(3), List(4, 4, 4),
List(5, 5), List(1, 1, 1), List(2))
scala> Vector(1,2,3,4,1,2,3,1,2,1).groupedWhile(_ < _)
res1: scala.collection.immutable.Vector[scala.collection.immutable.Vector[Int]] =
Vector(Vector(1, 2, 3, 4), Vector(1, 2, 3), Vector(1, 2), Vector(1))
有用!
唯一的问题是,我们通常不会将这些方法用于数组,因为这将需要连续两次隐式转换。有几种解决方法,包括为数组编写单独的隐式转换,转换为WrappedArray
,等等。
编辑:我最喜欢的处理数组和字符串的方法是使代码更加通用,然后使用适当的隐式转换使它们再次变得更加具体,从而使数组也可以工作。在这种情况下:
class GroupingCollection[A, C, D[C]](ca: C)(
implicit c2i: C => Iterable[A],
cbf: CanBuildFrom[C,C,D[C]],
cbfi: CanBuildFrom[C,A,C]
) {
def groupedWhile(p: (A,A) => Boolean): D[C] = {
val it = c2i(ca).iterator
val cca = cbf()
if (!it.hasNext) cca.result
else {
val as = cbfi()
var olda = it.next
as += olda
while (it.hasNext) {
val a = it.next
if (p(olda,a)) as += a
else { cca += as.result; as.clear; as += a }
olda = a
}
cca += as.result
}
cca.result
}
}
在这里,我们添加了一个隐式函数Iterable[A]
,C
该隐式函数为我们提供了from-对于大多数集合来说,它只是身份(例如,List[A]
已经是一个Iterable[A]
),但是对于数组,它将是一个真正的隐式转换。而且,因此,我们放弃了这样的要求:C[A] <: Iterable[A]
基本上,我们只是对<%
显式进行了要求,因此我们可以随意显式使用它,而不必让编译器为我们填充它。此外,我们放宽了我们的收藏集的限制,C[C[A]]
取而代之的是它是any D[C]
,我们稍后将填写它成为我们想要的。因为我们稍后将填写它,所以我们将其推到类级别而不是方法级别。否则,基本上是相同的。
现在的问题是如何使用它。对于常规收藏,我们可以:
implicit def collections_have_grouping[A, C[A]](ca: C[A])(
implicit c2i: C[A] => Iterable[A],
cbf: CanBuildFrom[C[A],C[A],C[C[A]]],
cbfi: CanBuildFrom[C[A],A,C[A]]
) = {
new GroupingCollection[A,C[A],C](ca)(c2i, cbf, cbfi)
}
在那里,现在我们插上C[A]
了C
和C[C[A]]
为D[C]
。请注意,我们确实需要对的显式泛型类型,new GroupingCollection
以便可以直接保持对应于哪些类型的类型。多亏了implicit c2i: C[A] => Iterable[A]
,它可以自动处理数组。
但是等等,如果我们想使用字符串怎么办?现在我们遇到了麻烦,因为您不能拥有“字符串字符串”。这是额外的抽象帮助的地方:我们可以调用D
适合于容纳字符串的东西。让我们选择Vector
,然后执行以下操作:
val vector_string_builder = (
new CanBuildFrom[String, String, Vector[String]] {
def apply() = Vector.newBuilder[String]
def apply(from: String) = this.apply()
}
)
implicit def strings_have_grouping(s: String)(
implicit c2i: String => Iterable[Char],
cbfi: CanBuildFrom[String,Char,String]
) = {
new GroupingCollection[Char,String,Vector](s)(
c2i, vector_string_builder, cbfi
)
}
我们需要一个新的CanBuildFrom
函数来处理字符串向量的构建(但这确实很容易,因为我们只需要调用Vector.newBuilder[String]
),然后我们需要填写所有类型,以便GroupingCollection
合理地键入。请注意,我们已经在[String,Char,String]
CanBuildFrom 周围浮动了,因此可以从char集合中创建字符串。
让我们尝试一下:
scala> List(true,false,true,true,true).groupedWhile(_ == _)
res1: List[List[Boolean]] = List(List(true), List(false), List(true, true, true))
scala> Array(1,2,5,3,5,6,7,4,1).groupedWhile(_ <= _)
res2: Array[Array[Int]] = Array(Array(1, 2, 5), Array(3, 5, 6, 7), Array(4), Array(1))
scala> "Hello there!!".groupedWhile(_.isLetter == _.isLetter)
res3: Vector[String] = Vector(Hello, , there, !!)