如何将丰富我的库模式应用到Scala集合?

Scala中最强大的模式之一是丰富我的库*模式,它使用隐式转换, 似乎添加方法到现有的类,而无需dynamic的方法parsing。 例如,如果我们希望所有的string都有方法spaces来计算它们有多less个空格字符,我们可以:

 class SpaceCounter(s: String) { def spaces = s.count(_.isWhitespace) } implicit def string_counts_spaces(s: String) = new SpaceCounter(s) scala> "How many spaces do I have?".spaces res1: Int = 5 

不幸的是,这种模式在处理generics集合时遇到了麻烦。 例如,一些问题已经被问及关于按顺序对集合进行分组的问题。 没有什么内置的工作在一个镜头,所以这似乎是一个理想的候选人丰富我图书馆模式使用generics集合C和通用元素typesA

 class SequentiallyGroupingCollection[A, C[A] <: Seq[A]](ca: C[A]) { def groupIdentical: C[C[A]] = { if (ca.isEmpty) C.empty[C[A]] else { val first = ca.head val (same,rest) = ca.span(_ == first) same +: (new SequentiallyGroupingCollection(rest)).groupIdentical } } } 

当然除外,这是行不通的 。 REPL告诉我们:

 <console>:12: error: not found: value C if (ca.isEmpty) C.empty[C[A]] ^ <console>:16: error: type mismatch; found : Seq[Seq[A]] required: C[C[A]] same +: (new SequentiallyGroupingCollection(rest)).groupIdentical ^ 

有两个问题:我们如何从一个空的C[A]列表(或从空中)获得一个C[C[A]] ? 我们如何从same +:行取代C[C[A]]而不是Seq[Seq[A]]

* 以前称为皮条客我的图书馆。

理解这个问题的关键是要认识在集合库中有两种不同的方式来构build和使用集合 。 一个是所有漂亮方法的公共集合界面。 另一个广泛用于创build集合库,但几乎从未在其外部使用的是build造者。

我们在丰富中遇到的问题与收集库本身在尝试返回相同types的集合时所面临的问题完全相同。 也就是说,我们要build立集合,但是当一般工作时,我们没有办法引用“与集合已经是同一types”。 所以我们需要build设者

现在的问题是:我们从哪里得到我们的build造者? 显而易见的地方是从collections本身。 这不起作用 。 我们已经决定,转向一个通用的集合,我们将会忘记集合的types。 所以即使集合可以返回一个能够生成更多我们想要的types的集合的构build器,它也不会知道types是什么。

相反,我们从CanBuildFrom获取我们的build造者的CanBuildFrom 。 这些专门用于匹配input和输出types,并为您提供适当types的构build器。

所以,我们有两个概念上的飞跃:

  1. 我们不使用标准的集合操作,我们正在使用构build器。
  2. 我们从隐式CanBuildFrom获取这些构build器,而不是直接从我们的集合中获取。

我们来看一个例子。

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

让我们把这个分开。 首先,为了构build集合集合,我们知道需要构build两种types的集合:每个组的C[C[A]]和将所有组合在一起的C[C[A]] 。 因此,我们需要两个build造者,一个build造C[A] ,一个build造C[A] ,另一个build造C[C[A]] 。 看看CanBuildFrom的types签名,我们看到

 CanBuildFrom[-From, -Elem, +To] 

这意味着CanBuildFrom想要知道我们开始的集合的types – 在我们的例子中,它是C[A] ,然后是生成的集合的元素和集合的types。 所以我们把它们作为隐式参数cbfcccbfc

意识到这一点,这是大部分的工作。 我们可以使用我们的CanBuildFrom来给我们build设者(所有你需要做的就是应用它们)。 而且一个build造者可以用+=来build立一个集合,把它转换成最终应该带有result的集合,然后自己清空,准备好以clear重新开始。 build设者开始空,这解决了我们的第一个编译错误,因为我们使用build设者而不是recursion,第二个错误也消失了。

最后一点细节 – 除了实际执行工作的algorithm之外 – 在隐式转换中。 请注意,我们使用new GroupingCollection[A,C]而不是[A,C[A]] 。 这是因为类声明是针对一个参数的C ,它将A传递给它自己。 所以我们只是把它的typesC ,并让它创buildC[A] 。 小细节,但如果你尝试另一种方式,你会得到编译时错误。

在这里,我已经使这个方法比“equal elements”集合更通用一些,相反,只要对连续元素的testing失败,方法就会将原始集合分开。

让我们看看我们的方法在行动:

 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等等。


编辑:我最喜欢的方法处理数组和string,这样做是为了使代码通用,然后使用适当的隐式转换,使它们更具体再次以数组工作。 在这个特殊情况下:

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

在这里,我们添加了一个隐式,它给了我们一个来自CIterable[A] – 对于大多数集合,这只是身份(例如List[A]已经是Iterable[A] ),但是对于数组来说,真正的隐式转换。 因此,我们已经放弃了C[A] <: Iterable[A]的要求 – 我们基本上只是对<% explicit的要求,所以我们可以明确地使用它而不是让编译器填充它在为我们。 另外,我们已经放宽了我们收集集合C[C[A]]限制,它是任何D[C] ,我们将在稍后填写这个限制,成为我们想要的。 因为我们将在稍后填补这个问题,所以我们把它推到了课程级别,而不是方法级别。 否则,基本上是一样的。

现在的问题是如何使用这个。 对于定期collections,我们可以:

 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]代表CC[C[A]]代表D[C] 。 请注意,我们确实需要明确的genericstypes调用new GroupingCollection以便它可以保持直接哪些types对应于什么。 由于implicit c2i: C[A] => Iterable[A] ,这会自动处理数组。

但是等等,如果我们想要使用string呢? 现在我们遇到了麻烦,因为你不能有一串“串”。 这是额外的抽象帮助的地方:我们可以调用D适合持有string的东西。 让我们selectVector ,然后执行以下操作:

 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来处理一个stringvector的构build(但是这很简单,因为我们只需要调用Vector.newBuilder[String] ),然后我们需要填写所有的types,这样GroupingCollection就是打字明智。 请注意,我们已经在[String,Char,String] CanBuildFrom周围进行了浮动操作,所以string可以由chars集合构成。

让我们试试看:

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

就这个提交而言 ,“丰富”Scala的集合要比Rex给出他的出色答案容易得多。 对于简单的情况,它可能看起来像这样,

 import scala.collection.generic.{ CanBuildFrom, FromRepr, HasElem } import language.implicitConversions class FilterMapImpl[A, Repr](val r : Repr)(implicit hasElem : HasElem[Repr, A]) { def filterMap[B, That](f : A => Option[B]) (implicit cbf : CanBuildFrom[Repr, B, That]) : That = r.flatMap(f(_).toSeq) } implicit def filterMap[Repr : FromRepr](r : Repr) = new FilterMapImpl(r) 

它在所有的GenTraversableLike添加了一个“相同的结果types”

 scala> val l = List(1, 2, 3, 4, 5) l: List[Int] = List(1, 2, 3, 4, 5) scala> l.filterMap(i => if(i % 2 == 0) Some(i) else None) res0: List[Int] = List(2, 4) scala> val a = Array(1, 2, 3, 4, 5) a: Array[Int] = Array(1, 2, 3, 4, 5) scala> a.filterMap(i => if(i % 2 == 0) Some(i) else None) res1: Array[Int] = Array(2, 4) scala> val s = "Hello World" s: String = Hello World scala> s.filterMap(c => if(c >= 'A' && c <= 'Z') Some(c) else None) res2: String = HW 

对于这个问题的例子,解决scheme现在看起来像,

 class GroupIdenticalImpl[A, Repr : FromRepr](val r: Repr) (implicit hasElem : HasElem[Repr, A]) { def groupIdentical[That](implicit cbf: CanBuildFrom[Repr,Repr,That]): That = { val builder = cbf(r) def group(r: Repr) : Unit = { val first = r.head val (same, rest) = r.span(_ == first) builder += same if(!rest.isEmpty) group(rest) } if(!r.isEmpty) group(r) builder.result } } implicit def groupIdentical[Repr : FromRepr](r: Repr) = new GroupIdenticalImpl(r) 

示例REPL会话,

 scala> val l = List(1, 1, 2, 2, 3, 3, 1, 1) l: List[Int] = List(1, 1, 2, 2, 3, 3, 1, 1) scala> l.groupIdentical res0: List[List[Int]] = List(List(1, 1),List(2, 2),List(3, 3),List(1, 1)) scala> val a = Array(1, 1, 2, 2, 3, 3, 1, 1) a: Array[Int] = Array(1, 1, 2, 2, 3, 3, 1, 1) scala> a.groupIdentical res1: Array[Array[Int]] = Array(Array(1, 1),Array(2, 2),Array(3, 3),Array(1, 1)) scala> val s = "11223311" s: String = 11223311 scala> s.groupIdentical res2: scala.collection.immutable.IndexedSeq[String] = Vector(11, 22, 33, 11) 

再一次请注意,已经观察到相同的结果types原则,就像在groupIdentical上直接定义GenTraversableLike

从这个angular度来看, 这个魔法咒语与迈尔斯给出了很好的答案时略有不同。

以下的作品,但它是典型的? 我希望其中的一个经典会纠正它。 (或者说,炮,其中一个大枪。)如果视图边界是一个上限,你失去了应用程序数组和string。 如果边界是GenTraversableLike或TraversableLike似乎并不重要; 但IsTraversableLike为您提供了一个GenTraversableLike。

 import language.implicitConversions import scala.collection.{ GenTraversable=>GT, GenTraversableLike=>GTL, TraversableLike=>TL } import scala.collection.generic.{ CanBuildFrom=>CBF, IsTraversableLike=>ITL } class GroupIdenticalImpl[A, R <% GTL[_,R]](val r: GTL[A,R]) { def groupIdentical[That](implicit cbf: CBF[R, R, That]): That = { val builder = cbf(r.repr) def group(r: GTL[_,R]) { val first = r.head val (same, rest) = r.span(_ == first) builder += same if (!rest.isEmpty) group(rest) } if (!r.isEmpty) group(r) builder.result } } implicit def groupIdentical[A, R <% GTL[_,R]](r: R)(implicit fr: ITL[R]): GroupIdenticalImpl[fr.A, R] = new GroupIdenticalImpl(fr conversion r) 

拥有九条生命的猫不止一种方法。 这个版本说,一旦我的源代码被转换成GenTraversableLike,只要我可以从GenTraversable生成结果,就这么做。 我对我的旧版Repr不感兴趣。

 class GroupIdenticalImpl[A, R](val r: GTL[A,R]) { def groupIdentical[That](implicit cbf: CBF[GT[A], GT[A], That]): That = { val builder = cbf(r.toTraversable) def group(r: GT[A]) { val first = r.head val (same, rest) = r.span(_ == first) builder += same if (!rest.isEmpty) group(rest) } if (!r.isEmpty) group(r.toTraversable) builder.result } } implicit def groupIdentical[A, R](r: R)(implicit fr: ITL[R]): GroupIdenticalImpl[fr.A, R] = new GroupIdenticalImpl(fr conversion r) 

这第一次尝试包括一个丑陋的Repr到GenTraversableLike的转换。

 import language.implicitConversions import scala.collection.{ GenTraversableLike } import scala.collection.generic.{ CanBuildFrom, IsTraversableLike } type GT[A, B] = GenTraversableLike[A, B] type CBF[A, B, C] = CanBuildFrom[A, B, C] type ITL[A] = IsTraversableLike[A] class FilterMapImpl[A, Repr](val r: GenTraversableLike[A, Repr]) { def filterMap[B, That](f: A => Option[B])(implicit cbf : CanBuildFrom[Repr, B, That]): That = r.flatMap(f(_).toSeq) } implicit def filterMap[A, Repr](r: Repr)(implicit fr: ITL[Repr]): FilterMapImpl[fr.A, Repr] = new FilterMapImpl(fr conversion r) class GroupIdenticalImpl[A, R](val r: GT[A,R])(implicit fr: ITL[R]) { def groupIdentical[That](implicit cbf: CBF[R, R, That]): That = { val builder = cbf(r.repr) def group(r0: R) { val r = fr conversion r0 val first = r.head val (same, other) = r.span(_ == first) builder += same val rest = fr conversion other if (!rest.isEmpty) group(rest.repr) } if (!r.isEmpty) group(r.repr) builder.result } } implicit def groupIdentical[A, R](r: R)(implicit fr: ITL[R]): GroupIdenticalImpl[fr.A, R] = new GroupIdenticalImpl(fr conversion r) 
Interesting Posts