Scala之集合的理解和應用案例

  • 可變集合和不可變集合

Scala 集合類系統地區分了可變的不可變的集合;

可變集合可以在適當的地方被更新或擴展。這意味着你可以修改,添加,移除一個集合的元素。默認情況下,Scala 一直採用不可變集合類。

不可變集合類,相比之下,永遠不會改變。因此,它是線程安全的;不過,你仍然可以模擬添加,移除或更新操作。但是這些操作將在每一種情況下都返回一個新的集合,同時使原來的集合不發生改變。如果你想要同時使用可變和不可變集合類,只導入collection.mutable包即可。

import scala.collection.mutable

下面的圖表顯示了scala.collection包中所有的集合類。這些都是高級抽象類或特性,它們通常具備和不可變實現一樣的可變實現。

collections.pnguploading.4e448015.gif轉存失敗重新上傳取消

下面的圖表顯示scala.collection.immutable中的所有集合類。

collections.immutable.pnguploading.4e448015.gif轉存失敗重新上傳取消

下面的圖表顯示scala.collection.mutable中的所有集合類。

collections.mutable.pnguploading.4e448015.gif轉存失敗重新上傳取消

  • Traversable

 Traversable(遍歷)是容器(collection)類的最高級別特性,它唯一的抽象操作是foreach:

object TraverableDemo extends Traversable[String] {
  override def foreach[U](f: String => U): Unit = ???
}

Traversable同時定義的很多具體方法,如下表所示:

相加操作++(addition)表示把兩個traversable對象附加在一起或者把一個迭代器的所有元素添加到traversable對象的尾部。

Map操作有map,flatMap和collect,它們可以通過對容器中的元素進行某些運算來生成一個新的容器。

轉換器(Conversion)操作包括toArray,toList,toIterable,toSeq,toIndexedSeq,toStream,toSet,和toMap,它們可以按照某種特定的方法對一個Traversable 容器進行轉換。等容器類型已經與所需類型相匹配的時候,所有這些轉換器都會不加改變的返回該容器。例如,對一個list使用toList,返回的結果就是list本身。

拷貝(Copying)操作有copyToBuffer和copyToArray。從字面意思就可以知道,它們分別用於把容器中的元素元素拷貝到一個緩衝區或者數組裏。

Size info操作包括有isEmpty,nonEmpty,size和hasDefiniteSize。Traversable容器有有限和無限之分。比方說,自然數流Stream.from(0)就是一個無限的traversable 容器。hasDefiniteSize方法能夠判斷一個容器是否可能是無限的。若hasDefiniteSize返回值爲ture,容器肯定有限。若返回值爲false,根據完整信息才能判斷容器(collection)是無限還是有限。

元素檢索(Element Retrieval)操作有head,last,headOption,lastOption和find。這些操作可以查找容器的第一個元素或者最後一個元素,或者第一個符合某種條件的元素。注意,儘管如此,但也不是所有的容器都明確定義了什麼是“第一個”或”最後一個“。例如,通過哈希值儲存元素的哈希集合(hashSet),每次運行哈希值都會發生改變。在這種情況下,程序每次運行都可能會導致哈希集合的”第一個“元素髮生變化。如果一個容器總是以相同的規則排列元素,那這個容器是有序的。大多數容器都是有序的,但有些不是(例如哈希集合)– 排序會造成一些額外消耗。排序對於重複性測試和輔助調試是不可或缺的。這就是爲什麼Scala容器中的所有容器類型都把有序作爲可選項。例如,帶有序性的HashSet就是LinkedHashSet。

子容器檢索(sub-collection Retrieval)操作有tail,init,slice,take,drop,takeWhilte,dropWhile,filter,filteNot和withFilter。它們都可以通過範圍索引或一些論斷的判斷返回某些子容器。

拆分(Subdivision)操作有splitAt,span,partition和groupBy,它們用於把一個容器(collection)裏的元素分割成多個子容器。

元素測試(Element test)包括有exists,forall和count,它們可以用一個給定論斷來對容器中的元素進行判斷。

摺疊(Folds)操作有foldLeft,foldRight,/:,:\,reduceLeft和reduceRight,用於對連續性元素的二進制操作。

特殊摺疊(Specific folds)包括sum, product, min, max。它們主要用於特定類型的容器(數值或比較)。

字符串(String)操作有mkString,addString和stringPrefix,可以將一個容器通過可選的方式轉換爲字符串。

視圖(View)操作包含兩個view方法的重載體。

  • 序列TRAIT

Seq trait用於表示序列。所謂序列,指的是一類具有一定長度的可迭代訪問的對象,其中每個元素均帶有一個從0開始計數的固定索引位置。

特性(trait) Seq 具有兩個子特徵(subtrait) LinearSeqIndexedSeq。它們不添加任何新的操作,但都提供不同的性能特點:線性序列具有高效的 head 和 tail 操作,而索引序列具有高效的apply, length, 和 (如果可變) update操作。

常用線性序列有 scala.collection.immutable.Listscala.collection.immutable.Stream

常用索引序列有 scala.Array scala.collection.mutable.ArrayBuffer

Vector 類提供一個在索引訪問和線性訪問之間有趣的折中。它同時具有高效的恆定時間的索引開銷,和恆定時間的線性訪問開銷。正因爲如此,對於混合訪問模式,vector是一個很好的基礎。

  • 緩衝器

Buffers是可變序列一個重要的種類。它們不僅允許更新現有的元素,而且允許元素的插入、移除和在buffer尾部高效地添加新元素。buffer 支持的主要新方法有:用於在尾部添加元素的 += 和 ++=;用於在前方添加元素的+=: ++=: ;用於插入元素的 insertinsertAll;以及用於刪除元素的remove 和 -=。如下表所示。

ListBuffer和ArrayBuffer是常用的buffer實現 。顧名思義,ListBuffer依賴列表(List),支持高效地將它的元素轉換成列表。而ArrayBuffer依賴數組(Array),能快速地轉換成數組。

  • 集合Set

集合是不包含重複元素的可迭代對象。

與不變集合一樣,可變集合也提供了+++操作符來添加元素,---用來刪除元素。但是這些操作在可變集合中通常很少使用,因爲這些操作都要通過集合的拷貝來實現。可變集合提供了更有效率的更新方法,+=-=。 s += elem,添加元素elem到集合s中,並返回產生變化後的集合作爲運算結果。同樣的,s -= elem 執行從集合s中刪除元素elem的操作,並返回產生變化後的集合作爲運算結果。除了+=-=之外還有從可遍歷對象集合或迭代器集合中添加和刪除所有元素的批量操作符++=--=

可變集合同樣提供作爲 += 和 -= 的變型方法,add 和 remove,它們的不同之處在於 add 和 remove 會返回一個表明運算是否對集合有作用的Boolean值.

目前可變集合默認使用哈希表來存儲集合元素,非可變集合則根據元素個數的不同,使用不同的方式來實現。空集用單例對象來表示。元素個數小於等於4的集合可以使用單例對象來表達,元素作爲單例對象的字段來存儲。 元素超過4個,非可變集合就用哈希前綴樹(hash trie)來實現。

採用這種表示方法,較小的不可變集合(元素數不超過4)往往會比可變集合更加緊湊和高效。所以,在處理小尺寸的集合時,不妨試試不可變集合。

  • 有序集(SortedSet)

SortedSet 是指以特定的順序(這一順序可以在創建集合之初自由的選定)排列其元素(使用iterator或foreach)的集合。 SortedSet 的默認表示是有序二叉樹,即左子樹上的元素小於所有右子樹上的元素。這樣,一次簡單的順序遍歷能按增序返回集合中的所有元素。Scala的類 immutable.TreeSet 使用紅黑樹實現,它在維護元素順序的同時,也會保證二叉樹的平衡,即葉節點的深度差最多爲1。

  • 位集合(Bitset)

待續...

  • 映射Map

映射(Map)是一種可迭代的鍵值對結構(也稱映射或關聯)。Scala的Predef類提供了隱式轉換,允許使用另一種語法:key -> value,來代替(key, value)。如:Map("x" -> 24, "y" -> 25, "z" -> 26)等同於Map(("x", 24), ("y", 25), ("z", 26)),卻更易於閱讀。

  • 不可變的實體類

Scala中提供了多種具體的不可變集類供你選擇,這些類(maps, sets, sequences)實現的接口(traits)不同,比如是否能夠是無限(infinite)的,各種操作的速度也不一樣

  • List(列表)

列表List是一種有限的不可變序列式。提供了常數時間的訪問列表頭元素和列表尾的操作,並且提供了常數時間的構造新鏈表的操作,該操作將一個新的元素插入到列表的頭部。其他許多操作則和列表的長度成線性關係。

  • Stream(流)

Stream與List很相似,只不過其中的每一個元素都經過了一些簡單的計算處理。也正是因爲如此,stream結構可以無限長。只有那些被要求的元素纔會經過計算處理,除此以外stream結構的性能特性與List基本相同。

  • Vector(向量)

對於只需要處理數據結構頭結點的算法來說,List非常高效。可是相對於訪問、添加和刪除List頭結點只需要固定時間,訪問和修改頭結點之後元素所需要的時間則是與List深度線性相關的。

向量Vector是用來解決列表(list)不能高效的隨機訪問的一種結構。Vector結構能夠在“更高效”的固定時間內訪問到列表中的任意元素。雖然這個時間會比訪問頭結點或者訪問某數組元素所需的時間長一些,但至少這個時間也是個常量。因此,使用Vector的算法不必僅是小心的處理數據結構的頭結點。由於可以快速修改和訪問任意位置的元素,所以對Vector結構做寫操作很方便。

Vector結構通常被表示成具有高分支因子的樹(樹或者圖的分支因子是指數據結構中每個節點的子節點數目)。每一個樹節點包含最多32個vector元素或者至多32個子樹節點。包含最多32個元素的vector可以表示爲一個單一節點,而一個間接引用則可以用來表示一個包含至多32*32=1024個元素的vector。從樹的根節點經過兩跳到達葉節點足夠存下有2的15次方個元素的vector結構,經過3跳可以存2的20次方個,4跳2的25次方個,5跳2的30次方個。所以對於一般大小的vector數據結構,一般經過至多5次數組訪問就可以訪問到指定的元素。這也就是我們之前所提及的隨機數據訪問時“運行時間的相對高效”。

由於Vectors結構是不可變的,所以您不能通過修改vector中元素的方法來返回一個新的vector。儘管如此,您仍可以通過update方法從一個單獨的元素中創建出區別於給定數據結構的新vector結構:

scala> val vec = Vector(1, 2, 3)
vec: scala.collection.immutable.Vector[Int] = Vector(1, 2, 3)
scala> vec updated (2, 4)
res0: scala.collection.immutable.Vector[Int] = Vector(1, 2, 4)
scala> vec
res1: scala.collection.immutable.Vector[Int] = Vector(1, 2, 3)

由於vector在快速隨機選擇和快速隨機更新的性能方面做到很好的平衡,所以它目前正被用作不可變索引序列的默認實現方式。

  • Immutable stacks(不可變棧)

 如果您想要實現一個後入先出的序列,那您可以使用Stack。您可以使用push向棧中壓入一個元素,用pop從棧中彈出一個元素,用top查看棧頂元素而不用刪除它。所有的這些操作都僅僅耗費固定的運行時間。

不可變stack一般很少用在Scala編程中,因爲List結構已經能夠覆蓋到它的功能:push操作同List中的::基本相同,pop則對應着tail。

  • Immutable Queues(不可變隊列)

Queue是一種與stack很相似的數據結構,除了與stack的後入先出不同,Queue結構的是先入先出的。

  • Ranges (等差數列)

 [Range]表示的是一個有序的等差整數數列。比如說,“1,2,3,”就是一個Range,“5,8,11,14,”也是。在Scala中創建一個Range類,需要用到兩個預定義的方法to和by。

  • 具體的可變容器類

  • Array Buffers

一個ArrayBuffer緩衝包含數組和數組的大小。對數組緩衝的大多數操作,其速度與數組本身無異。因爲這些操作直接訪問、修改底層數組。另外,數組緩衝可以進行高效的尾插數據。追加操作均攤下來只需常量時間。因此,數組緩衝可以高效的建立一個有大量數據的容器,無論是否總有數據追加到尾部。

  • List Buffers

ListBuffer 類似於數組緩衝。區別在於前者內部實現是鏈表, 而非數組。如果你想把構造完的緩衝轉換爲列表,那就用列表緩衝,別用數組緩衝。

  • StringBuilders

數組緩衝用來構建數組,列表緩衝用來創建列表。類似地,StringBuilder 用來構造字符串。作爲常用的類,字符串構造器已導入到默認的命名空間。直接用 new StringBuilder就可創建字符串構造器 ,

  •  鏈表

鏈表是可變序列,它由一個個使用next指針進行鏈接的節點構成。它們的支持類是LinkedList。在大多數的編程語言中,null可以表示一個空鏈表,但是在Scalable集合中不是這樣。因爲就算是空的序列,也必須支持所有的序列方法。尤其是 LinkedList.empty.isEmpty 必須返回true,而不是拋出一個 NullPointerException 。空鏈表用一種特殊的方式編譯: 

它們的 next 字段指向它自身。鏈表像他們的不可變對象一樣,是最佳的順序遍歷序列。此外,鏈表可以很容易去插入一個元素或鏈接到另一個鏈表。

  • 數組

在Scala中,數組是一種特殊的collection。一方面,Scala數組與Java數組是一一對應的。即Scala數組Array[Int]可看作Java的Int[],Array[Double]可看作Java的double[],以及Array[String]可看作Java的String[]。但Scala數組比Java數組提供了更多內容。首先,Scala數組是一種泛型。即可以定義一個Array[T],T可以是一種類型參數或抽象類型。其次,Scala數組與Scala序列是兼容的 - 在需要Seq[T]的地方可由Array[T]代替。最後,Scala數組支持所有的序列操作。

請關注作者公衆號:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章