1. 程式人生 > >Scala 集合(四)

Scala 集合(四)

Scala Builder、CanBuildFrom和Like特質

1.Builder

我在前面幾篇有關集合的文章中曾提到,如果小心合理地使用,可變集合將是為了提高效能而做出的合理讓步。事實上,集合API 在內部使用了可變集合建立新的輸出集合,如在map 操作中就是如此。map 操作還使用了collection.mutable.Builder 特徵的實現,以構造新例項。

原始碼:

package scala.collection.mutable
trait Builder[-Elem, +To] extends scala.AnyRef with scala.collection.generic.Growable[Elem] {
  def +=(elem : Elem) : Builder.this.type
  def clear() : scala.Unit
  def result() : To
  def sizeHint(size : scala.Int) : scala.Unit = { /* compiled code */ }
  def sizeHint(coll : scala.collection.TraversableLike[_, _]) : scala.Unit = { /* compiled code */ }
  def sizeHint(coll : scala.collection.TraversableLike[_, _], delta : scala.Int) : scala.Unit = { /* compiled code */ }
  def sizeHintBounded(size : scala.Int, boundingColl : scala.collection.TraversableLike[_, _]) : scala.Unit = { /* compiled code */ }
  def mapResult[NewTo](f : scala.Function1[To, NewTo]) : scala.collection.mutable.Builder[Elem, NewTo] = { /* compiled code */ }
}

這種罕見的Builder.this.type 簽名是一個單例型別(singleton type)。它確保+= 方法在呼叫時只返回生Builder 的例項,也就是this。如果嘗試返回Builder 的一個新例項,那麼就無法通過型別檢查。

接下來做一個List 的builder 實現:

package cn.com.tengen.test.obj

import collection.mutable.Builder

class ListBuilder[T] extends Builder[T,List[T]]{
  private var storage = Vector.empty[T]

  override def +=(elem:T) ={
    storage = storage :+ elem
    this
  }

  override def clear(): Unit = {
    storage = Vector.empty[T]
  }

  override def result(): List[T] = storage.toList
}

object ListBuilder extends App{
  val lb = new ListBuilder[Int]
  (1 to 10) foreach (i => lb += i)
  println(lb.result)
}

輸出結果:

List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

2 CanBuildFrom

做一個List呼叫map的例子:

object Test extends App {
  var l = List(1, 2, 3, 4, 5, 6)
  l = l map (_ + 1)
  println(l)
}

我們看一下map:

首先map方法來自於:List


package scala.collection.immutable
sealed abstract class List[+A]() extends scala.collection.AbstractSeq[A] ... {
    ...
  final override def map[B, That](f : scala.Function1[A, B])(implicit bf : scala.collection.generic.CanBuildFrom[scala.collection.immutable.List[A], B, That]) : That = { 
    /* compiled code */ 
  }
    ...
}

結果發現map是被重寫的,追根溯源

scala.collection.AbstractSeq[A] -> scala.collection.AbstractIterable[A] ->  scala.collection.AbstractTraversable[A] -> scala.AnyRef with scala.collection.Traversable[A] -> scala.AnyRef with scala.collection.TraversableLike[A, scala.collection.Traversable[A]]

一層層的繼承終於發現map源於TraversableLike

package scala.collection
trait TraversableLike[+A, +Repr] extends scala.Any with ... {
  ...
  def map[B, That](f : scala.Function1[A, B])(implicit bf : scala.collection.generic.CanBuildFrom[Repr, B, That]) : That = {
    /* compiled code */ 
    }
 
  ...
}

scala.collection.immutable.List對map的重寫實質上就是泛型更具體一點,接下來就簡單的探討一下TraversableLike中的map方法:

Repr 是內部用來儲存元素的集合型別。B 是函式f 建立的元素型別。That 是我們想要建立的目標集合的型別引數,它可能與輸入的原始集合相同,也可能不同。

TraversableLike 對其子類(如List)一無所知,但它還是可以構造新的List 並將其返回,因為隱含的CanBuildFrom 例項封裝了所需要的細節。

CanBuildFrom 是建立Builder 例項的工廠的trait,由工廠來完成實際構造新集合的工作。使用CanBuildFrom 技術的一個缺點是額外增加了方法簽名的複雜度。然而,除了促進類似map 等操作在面向物件中的重用以外,CanBuildFrom 在其他方面使得構造更加模組化、通用化。

例如:CanBuildFrom 例項可以為返回的不同具體集合例項化多個Builder。通常它會返回相同型別的新集合,或者返回對給定元素來說更高效的子型別。

實現一個包含很多元素的對映表最好將鍵儲存在散列表中,同時提供均為O(1) 的儲存和查詢複雜度。然而,對於一個小對映,可能將元素直接存在陣列或列表中會更快,這時n 很小,O(n) 的查詢複雜度事實上比散列表的O(1) 複雜度還要快,這取決於散列表的常數開銷因子。

輸入集合型別不能用來作為輸出集合型別的情況還有其他形式。考慮下面的示例:

object Test extends App {
  val set = collection.BitSet(1, 2, 3, 4, 5)
  println(set.getClass+": "+set)
  var s = set map (_ + "a")
  println(s.getClass+": "+s)
}

輸出:

class scala.collection.immutable.BitSet$BitSet1: BitSet(1, 2, 3, 4, 5)
class scala.collection.immutable.TreeSet: TreeSet(1a, 2a, 3a, 4a, 5a)

在命令列看一下:

https://blog.csdn.net/u014646662/article/details/84302437已經介紹了BitSet集合,BitSet是非負整數的集合,Int加上String的結果是String,所以,如果將其元素轉為字串,隱含的CanBuildFrom 只能例項化不同於輸入的輸出集合。
在本例中, 輸出集合是SortedSet。

類似地,對於字串(字元序列),我們也可能遇到以下情形:
 


CanBuildFrom 的另一個好處是傳送其他上下文資訊的能力,這些上下文資訊可能是原集合不知道或不適合由原集合來傳遞的。例如:使用分散式計算API 時,特定的CanBuildFrom例項可能被用於構建那些適合序列化到遠端程序的集合。

3 Like特質

Builder 和CanBuildFrom 為輸出集合指定了型別引數。為了支援對這些引數的指定,也為了加強程式碼重用,你知道的大多數集合都混入了相應的 *Like  特質。該trait 可以新增合適的返回值型別,併為常見方法提供實現。

以下是collection.immutable.Seq 的宣告:

package scala.collection.immutable

trait Seq[+A] extends scala.AnyRef 
  with scala.collection.immutable.Iterable[A] 
  with scala.collection.Seq[A] 
  with scala.collection.generic.GenericTraversableTemplate[A, scala.collection.immutable.Seq] 
  with scala.collection.SeqLike[A, scala.collection.immutable.Seq[A]] 
  with scala.collection.Parallelizable[A, scala.collection.parallel.immutable.ParSeq[A]] {

}

需要注意的是,collection.SeqLike 同時用元素型別A 和Seq[A] 本身進行引數化。第二個引數用於約束在map 等方法中能夠使用的CanBuildFrom 例項。該trait 還實現了Seq 中大部分我們熟悉的方法。

SeqLike 原始碼:

package scala.collection
trait SeqLike[+A, +Repr] extends scala.Any with scala.collection.IterableLike[A, Repr] with scala.collection.GenSeqLike[A, Repr] with scala.collection.Parallelizable[A, scala.collection.parallel.ParSeq[A]] {
  protected[this] override def thisCollection : scala.collection.Seq[A] = { /* compiled code */ }
  protected[this] override def toCollection(repr : Repr) : scala.collection.Seq[A] = { /* compiled code */ }
  def length : scala.Int
  def apply(idx : scala.Int) : A
  protected[this] override def parCombiner : scala.collection.parallel.Combiner[A, scala.collection.parallel.ParSeq[A]] = { /* compiled code */ }
  def lengthCompare(len : scala.Int) : scala.Int = { /* compiled code */ }
  override def isEmpty : scala.Boolean = { /* compiled code */ }
  override def size : scala.Int = { /* compiled code */ }
  def segmentLength(p : scala.Function1[A, scala.Boolean], from : scala.Int) : scala.Int = { /* compiled code */ }
  def indexWhere(p : scala.Function1[A, scala.Boolean], from : scala.Int) : scala.Int = { /* compiled code */ }
  def lastIndexWhere(p : scala.Function1[A, scala.Boolean], end : scala.Int) : scala.Int = { /* compiled code */ }
  def permutations : scala.collection.Iterator[Repr] = { /* compiled code */ }
  def combinations(n : scala.Int) : scala.collection.Iterator[Repr] = { /* compiled code */ }
  def reverse : Repr = { /* compiled code */ }
  def reverseMap[B, That](f : scala.Function1[A, B])(implicit bf : scala.collection.generic.CanBuildFrom[Repr, B, That]) : That = { /* compiled code */ }
  def reverseIterator : scala.collection.Iterator[A] = { /* compiled code */ }
  def startsWith[B](that : scala.collection.GenSeq[B], offset : scala.Int) : scala.Boolean = { /* compiled code */ }
  def endsWith[B](that : scala.collection.GenSeq[B]) : scala.Boolean = { /* compiled code */ }
  def indexOfSlice[B >: A](that : scala.collection.GenSeq[B]) : scala.Int = { /* compiled code */ }
  def indexOfSlice[B >: A](that : scala.collection.GenSeq[B], from : scala.Int) : scala.Int = { /* compiled code */ }
  def lastIndexOfSlice[B >: A](that : scala.collection.GenSeq[B]) : scala.Int = { /* compiled code */ }
  def lastIndexOfSlice[B >: A](that : scala.collection.GenSeq[B], end : scala.Int) : scala.Int = { /* compiled code */ }
  def containsSlice[B](that : scala.collection.GenSeq[B]) : scala.Boolean = { /* compiled code */ }
  def contains[A1 >: A](elem : A1) : scala.Boolean = { /* compiled code */ }
  override def union[B >: A, That](that : scala.collection.GenSeq[B])(implicit bf : scala.collection.generic.CanBuildFrom[Repr, B, That]) : That = { /* compiled code */ }
  def diff[B >: A](that : scala.collection.GenSeq[B]) : Repr = { /* compiled code */ }
  def intersect[B >: A](that : scala.collection.GenSeq[B]) : Repr = { /* compiled code */ }
  def distinct : Repr = { /* compiled code */ }
  def patch[B >: A, That](from : scala.Int, patch : scala.collection.GenSeq[B], replaced : scala.Int)(implicit bf : scala.collection.generic.CanBuildFrom[Repr, B, That]) : That = { /* compiled code */ }
  def updated[B >: A, That](index : scala.Int, elem : B)(implicit bf : scala.collection.generic.CanBuildFrom[Repr, B, That]) : That = { /* compiled code */ }
  def +:[B >: A, That](elem : B)(implicit bf : scala.collection.generic.CanBuildFrom[Repr, B, That]) : That = { /* compiled code */ }
  def :+[B >: A, That](elem : B)(implicit bf : scala.collection.generic.CanBuildFrom[Repr, B, That]) : That = { /* compiled code */ }
  def padTo[B >: A, That](len : scala.Int, elem : B)(implicit bf : scala.collection.generic.CanBuildFrom[Repr, B, That]) : That = { /* compiled code */ }
  def corresponds[B](that : scala.collection.GenSeq[B])(p : scala.Function2[A, B, scala.Boolean]) : scala.Boolean = { /* compiled code */ }
  def sortWith(lt : scala.Function2[A, A, scala.Boolean]) : Repr = { /* compiled code */ }
  def sortBy[B](f : scala.Function1[A, B])(implicit ord : scala.math.Ordering[B]) : Repr = { /* compiled code */ }
  def sorted[B >: A](implicit ord : scala.math.Ordering[B]) : Repr = { /* compiled code */ }
  override def toSeq : scala.collection.Seq[A] = { /* compiled code */ }
  def indices : scala.collection.immutable.Range = { /* compiled code */ }
  override def view : scala.AnyRef with scala.collection.SeqView[A, Repr] = { /* compiled code */ }
  override def view(from : scala.Int, until : scala.Int) : scala.collection.SeqView[A, Repr] = { /* compiled code */ }
  override def toString() : _root_.scala.Predef.String = { /* compiled code */ }
}
object SeqLike extends scala.AnyRef {
  def indexOf[B](source : scala.collection.Seq[B], sourceOffset : scala.Int, sourceCount : scala.Int, target : scala.collection.Seq[B], targetOffset : scala.Int, targetCount : scala.Int, fromIndex : scala.Int) : scala.Int = { /* compiled code */ }
  def lastIndexOf[B](source : scala.collection.Seq[B], sourceOffset : scala.Int, sourceCount : scala.Int, target : scala.collection.Seq[B], targetOffset : scala.Int, targetCount : scala.Int, fromIndex : scala.Int) : scala.Int = { /* compiled code */ }
}

簡單總結一下:

(1) 用Builder 抽象了構造。
(2) 用CanBuildFrom 提供隱含的工廠,用於構造適合給定上下文的生成器例項。
(3) Like 特徵為Builder 和CanBuildFrom 新增必須的返回值型別引數,並提供大部分的方法實現。