Scala之——Scala容器庫(Scala’s Collections Library)
簡介(Introduction)
Martin Odersky和Lex Spoon
在許多人看來,新的集合框架是Scala 2.8中最顯著的改進。此前Scala也有集合(實際上新框架大部分地相容了舊框架),但2.8中的集合類在通用性、一致性和功能的豐富性上更勝一籌。
即使粗看上去集合新增的內容比較微妙,但這些改動卻足以對開發者的程式設計風格造成深遠的影響。實際上,就好像你從事一個高層次的程式,而此程式的基本的構建塊的元素被整個集合代替。適應這種新的程式設計風格需要一個過程。幸運的是,新的Scala集合得益於幾個新的幾個漂亮的屬性,從而它們易於使用、簡潔、安全、快速、通用。
易用性:由20-50個方法的小詞彙量,足以在幾個操作內解決大部分的集合問題。沒有必要被複雜的迴圈或遞迴所困擾。持久化的集合類和無副作用的操作意味著你不必擔心新資料會意外的破壞已經存在的集合。迭代器和集合更新之間的干擾會被消除!
簡潔:你可以通過單獨的一個詞來執行一個或多個迴圈。你也可以用輕量級的語法和組合輕鬆地快速表達功能性的操作,以致結果看起來像一個自定義的代數。
安全:這一問題必須被熟練的理解,Scala集合的靜態型別和函式性質意味著你在編譯的時候就可以捕獲絕大多數錯誤。原因是(1)、集合操作本身被大量的使用,是測試良好的。(2)、集合的用法要求輸入和輸出要顯式作為函式引數和結果。(3)這些顯式的輸入輸出會受到靜態型別檢查。最終,絕大部分的誤用將會顯示為型別錯誤。這是很少見的的有數百行的程式的首次執行。
快速:集合操作已經在類庫裡是調整和優化過。因此,使用集合通常是比較高效的。你也許能夠通過手動調整資料結構和操作來做的好一點,但是你也可能會由於一些次優的實現而做的更糟。不僅如此,集合類最近已經能支援在多核處理器上並行運算。並行集合類支援有序集合的相同操作,因此沒有新的操作需要學習也沒有程式碼需要重寫。你可以簡單地通過呼叫標準方法來把有序集合優化為一個並行集合。
通用:集合類提供了相同的操作在一些型別上,確實如此。所以,你可以用相當少的詞彙完成大量的工作。例如,一個字串從概念上講就是一個字元序列。因此,在Scala集合類中,字串支援所有的序列操作。同樣的,陣列也是支援的。
例子:這有一行程式碼演示了Scala集合類的先進性。
val (minors, adults) = people partition (_.age < 18)
這個操作是清晰的:通過他們的age(年齡)把這個集合people拆分到到miors(未成年人)和adults(成年人)中。由於這個拆分方法是被定義在根集合型別TraversableLike類中,這部分程式碼服務於任何型別的集合,包括陣列。例子執行的結果就是miors和adults集合與people集合的型別相同。
這個程式碼比使用傳統的類執行一到三個迴圈更加簡明(三個迴圈處理一個數組,是由於中間結果需要有其它地方做快取)。一旦你已經學習了基本的集合詞彙,你將也發現寫這種程式碼顯式的迴圈更簡單和更安全。而且,這個拆分操作是非常快速,並且在多核處理器上採用並行集合類達到更快的速度(並行集合類已經Scala 2.9的一部分發布)。
本文件從一個使用者的角度出發,提供了一個關於Scala集合類的 API的深入討論。它將帶你體驗它定義的所有的基礎類和方法。
Mutable和-Immutable-集合.mdMutable 和 Immutable 集合
Scala 集合類系統地區分了可變的和不可變的集合。可變集合可以在適當的地方被更新或擴充套件。這意味著你可以修改,新增,移除一個集合的元素。而不可變集合類,相比之下,永遠不會改變。不過,你仍然可以模擬新增,移除或更新操作。但是這些操作將在每一種情況下都返回一個新的集合,同時使原來的集合不發生改變。
所有的集合類都可以在包scala.collection
或scala.collection.mutable
,scala.collection.immutable
,scala.collection.generic
中找到。客戶端程式碼需要的大部分集合類都獨立地存在於3種變體中,它們位於scala.collection
, scala.collection.immutable
, scala.collection.mutable
包。每一種變體在可變性方面都有不同的特徵。
scala.collection.immutable
包是的集合類確保不被任何物件改變。例如一個集合建立之後將不會改變。因此,你可以相信一個事實,在不同的點訪問同一個集合的值,你將總是得到相同的元素。。
scala.collection.mutable
包的集合類則有一些操作可以修改集合。所以處理可變集合意味著你需要去理解哪些程式碼的修改會導致集合同時改變。
scala.collection
包中的集合,既可以是可變的,也可以是不可變的。例如:collection.IndexedSeq[T]] 就是 collection.immutable.IndexedSeq[T] 和collection.mutable.IndexedSeq[T]這兩類的超類。scala.collection
包中的根集合類中定義了相同的介面作為不可變集合類,同時,scala.collection.mutable
包中的可變集合類代表性的添加了一些有輔助作用的修改操作到這個immutable 介面。
根集合類與不可變集合類之間的區別是不可變集合類的客戶端可以確保沒有人可以修改集合。然而,根集合類的客戶端僅保證不修改集合本身。即使這個集合類沒有提供修改集合的靜態操作,它仍然可能在執行時作為可變集合被其它客戶端所修改。
預設情況下,Scala 一直採用不可變集合類。例如,如果你僅寫了Set
而沒有任何加字首也沒有從其它地方匯入Set
,你會得到一個不可變的set
,另外如果你寫迭代,你也會得到一個不可變的迭代集合類,這是由於這些類在從scala中匯入的時候都是預設繫結的。為了得到可變的預設版本,你需要顯式的宣告collection.mutable.Set
或collection.mutable.Iterable
.
一個有用的約定,如果你想要同時使用可變和不可變集合類,只匯入collection.mutable包即可。
import scala.collection.mutable //匯入包scala.collection.mutable
然而,像沒有字首的Set這樣的關鍵字, 仍然指的是一個不可變集合,然而mutable.Set
指的是可變的副本(可變集合)。
集合樹的最後一個包是collection.generic
。這個包包含了集合的構建塊。集合類延遲了collection.generic
類中的部分操作實現,另一方面集合框架的使用者需要引用collection.generic
中類在異常情況中。
為了方便和向後相容性,一些匯入型別在包scala中有別名,所以你能通過簡單的名字使用它們而不需要import。這有一個例子是List 型別,它可以用以下兩種方法使用,如下:
scala.collection.immutable.List // 這是它的定義位置
scala.List //通過scala 包中的別名
List // 因為scala._
// 總是是被自動匯入。
下面的圖表顯示了scala.collection
包中所有的集合類。這些都是高階抽象類或特性,它們通常具備和不可變實現一樣的可變實現。
下面的圖表顯示scala.collection.immutable中的所有集合類
下面的圖表顯示scala.collection.mutable中的所有集合類。
(以上三個圖表由Matthias生成, 來自decodified.com)。
集合API概述
大多數重要的集合類都被展示在了上表。而且這些類有很多的共性。例如,每一種集合都能用相同的語法建立,寫法是集合類名緊跟著元素。
Traversable(1, 2, 3)
Iterable("x", "y", "z")
Map("x" -> 24, "y" -> 25, "z" -> 26)
Set(Color.red, Color.green, Color.blue)
SortedSet("hello", "world")
Buffer(x, y, z)
IndexedSeq(1.0, 2.0)
LinearSeq(a, b, c)
相同的原則也應用於特殊的集合實現,例如:
List(1, 2, 3)
HashMap("x" -> 24, "y" -> 25, "z" -> 26)
所有這些集合類都通過相同的途徑,用toString方法展示出來。
Traversable類提供了所有集合支援的API,同時,對於特殊型別也是有意義的。例如,Traversable類 的map方法會返回另一個Traversable物件作為結果,但是這個結果型別在子類中被重寫了。例如,在一個List上呼叫map會又生成一個List,在Set上呼叫會再生成一個Set,以此類推。
scala> List(1, 2, 3) map (_ + 1)
res0: List[Int] = List(2, 3, 4)
scala> Set(1, 2, 3) map (_ * 2)
res0: Set[Int] = Set(2, 4, 6)
在集合類庫中,這種在任何地方都實現了的行為,被稱之為返回型別一致原則。
大多數類在集合樹中存在這於三種變體:root, mutable 和immutable。唯一的例外是緩衝區特徵,它僅在於mutable集合。
Trait Traversable
Traversable(遍歷)是容器(collection)類的最高級別特性,它唯一的抽象操作是foreach:
def foreach[U](/DOC_Scala/chinese_scala_offical_document/file/f: Elem => U)
需要實現Traversable的容器(collection)類僅僅需要定義與之相關的方法,其他所有方法可都可以從Traversable中繼承。
foreach方法用於遍歷容器(collection)內的所有元素和每個元素進行指定的操作(比如說f操作)。操作型別是Elem => U,其中Elem是容器(collection)中元素的型別,U是一個任意的返回值型別。對f的呼叫僅僅是容器遍歷的副作用,實際上所有函式f的計算結果都被foreach拋棄了。
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方法的過載體。一個view物件可以當作是一個容器客觀地展示。接下來將會介紹更多有關檢視內容。
Traversable物件的操作
WHAT IT IS | WHAT IT DOES |
---|---|
抽象方法: | |
xs foreach f | 對xs中的每一個元素執行函式f |
加運算(Addition): | |
xs ++ ys | 生成一個由xs和ys中的元素組成容器。ys是一個TraversableOnce容器,即Taversable型別或迭代器。 |
Maps: | |
xs map f | 通過函式xs中的每一個元素呼叫函式f來生成一個容器。 |
xs flatMap f | 通過對容器xs中的每一個元素呼叫作為容器的值函式f,在把所得的結果連線起來作為一個新的容器。 |
xs collect f | 通過對每個xs中的符合定義的元素呼叫偏函式f,並把結果收集起來生成一個集合。 |
轉換(Conversions): | |
xs.toArray | 把容器轉換為一個數組 |
xs.toList | 把容器轉換為一個list |
xs.toIterable | 把容器轉換為一個迭代器。 |
xs.toSeq | 把容器轉換為一個序列 |
xs.toIndexedSeq | 把容器轉換為一個索引序列 |
xs.toStream | 把容器轉換為一個延遲計算的流。 |
xs.toSet | 把容器轉換為一個集合(Set)。 |
xs.toMap | 把由鍵/值對組成的容器轉換為一個對映表(map)。如果該容器並不是以鍵/值對作為元素的,那麼呼叫這個操作將會導致一個靜態型別的錯誤。 |
拷貝(Copying): | |
xs copyToBuffer buf | 把容器的所有元素拷貝到buf緩衝區。 |
xs copyToArray(arr, s, n) | 拷貝最多n個元素到陣列arr的座標s處。引數s,n是可選項。 |
大小判斷(Size info): | |
xs.isEmpty | 測試容器是否為空。 |
xs.nonEmpty | 測試容器是否包含元素。 |
xs.size | 計算容器內元素的個數。 |
xs.hasDefiniteSize | 如果xs的大小是有限的,則為true。 |
元素檢索(Element Retrieval): | |
xs.head | 返回容器內第一個元素(或其他元素,若當前的容器無序)。 |
xs.headOption | xs選項值中的第一個元素,若xs為空則為None。 |
xs.last | 返回容器的最後一個元素(或某個元素,如果當前的容器無序的話)。 |
xs.lastOption | xs選項值中的最後一個元素,如果xs為空則為None。 |
xs find p | 查詢xs中滿足p條件的元素,若存在則返回第一個元素;若不存在,則為空。 |
子容器(Subcollection): | |
xs.tail | 返回由除了xs.head外的其餘部分。 |
xs.init | 返回除xs.last外的其餘部分。 |
xs slice (from, to) | 返回由xs的一個片段索引中的元素組成的容器(從from到to,但不包括to)。 |
xs take n | 由xs的第一個到第n個元素(或當xs無序時任意的n個元素)組成的容器。 |
xs drop n | 由除了xs take n以外的元素組成的容器。 |
xs takeWhile p | 容器xs中最長能夠滿足斷言p的字首。 |
xs dropWhile p | 容器xs中除了xs takeWhile p以外的全部元素。 |
xs filter p | 由xs中滿足條件p的元素組成的容器。 |
xs withFilter p | 這個容器是一個不太嚴格的過濾器。子容器呼叫map,flatMap,foreach和withFilter只適用於xs中那些的滿足條件p的元素。 |
xs filterNot p | 由xs中不滿足條件p的元素組成的容器。 |
拆分(Subdivision): | |
xs splitAt n | 把xs從指定位置的拆分成兩個容器(xs take n和xs drop n)。 |
xs span p | 根據一個斷言p將xs拆分為兩個容器(xs takeWhile p, xs.dropWhile p)。 |
xs partition p | 把xs分割為兩個容器,符合斷言p的元素賦給一個容器,其餘的賦給另一個(xs filter p, xs.filterNot p)。 |
xs groupBy f | 根據判別函式f把xs拆分一個到容器(collection)的map中。 |
條件元素(Element Conditions): | |
xs forall p | 返回一個布林值表示用於表示斷言p是否適用xs中的所有元素。 |
xs exists p | 返回一個布林值判斷xs中是否有部分元素滿足斷言p。 |
xs count p | 返回xs中符合斷言p條件的元素個數。 |
摺疊(Fold): | |
(z /: xs)(op) | 在xs中,對由z開始從左到右的連續元素應用二進位制運算op。 |
(xs :\ z)(op) | 在xs中,對由z開始從右到左的連續元素應用二進位制運算op |
xs.foldLeft(z)(op) | 與(z /: xs)(op)相同。 |
xs.foldRight(z)(op) | 與 (xs :\ z)(op)相同。 |
xs reduceLeft op | 非空容器xs中的連續元素從左至右呼叫二進位制運算op。 |
xs reduceRight op | 非空容器xs中的連續元素從右至左呼叫二進位制運算op。 |
特殊摺疊(Specific Fold): | |
xs.sum | 返回容器xs中數字元素的和。 |
xs.product | xs返回容器xs中數字元素的積。 |
xs.min | 容器xs中有序元素值中的最小值。 |
xs.max | 容器xs中有序元素值中的最大值。 |
字串(String): | |
xs addString (b, start, sep, end) | 把一個字串加到StringBuilder物件b中,該字串顯示為將xs中所有元素用分隔符sep連線起來並封裝在start和end之間。其中start,end和sep都是可選的。 |
xs mkString (start, sep, end) | 把容器xs轉換為一個字串,該字串顯示為將xs中所有元素用分隔符sep連線起來並封裝在start和end之間。其中start,end和sep都是可選的。 |
xs.stringPrefix | 返回一個字串,該字串是以容器名開頭的xs.toString。 |
檢視(View): | |
xs.view | 通過容器xs生成一個檢視。 |
xs view (from, to) | 生成一個表示在指定索引範圍內的xs元素的檢視。 |
Trait Iterable
自下而上的容器(collection)層次結構具有可迭代的Trait。Trait的所有方法可定義為一個抽象方法,逐個生成容器(collection)元素迭代器。Traversable Trait的foreach方法實現了迭代器的Iterable。下面是具體的實現。
def foreach[U](/DOC_Scala/chinese_scala_offical_document/file/f: Elem => U): Unit = {
val it = iterator
while (it.hasNext) f(it.next())
}
許多Iterable 的子類覆寫了Iteable的foreach標準實現,因為它們提供了更多有效的實現。記住,由於效能問題,foreach是Traversable所有操作能夠實現的基礎。
Iterable有兩個方法返回迭代器:grouped和sliding。然而,這些迭代器返回的不是單個元素,而是原容器(collection)元素的全部子序列。這些最大的子序列作為引數傳給這些方法。grouped方法返回元素的增量分塊,sliding方法生成一個滑動元素的視窗。兩者之間的差異通過REPL的作用能夠清楚看出。
scala> val xs = List(1, 2, 3, 4, 5)
xs: List[Int] = List(1, 2, 3, 4, 5)
scala> val git = xs grouped 3
git: Iterator[List[Int]] = non-empty iterator
scala> git.next()
res3: List[Int] = List(1, 2, 3)
scala> git.next()
res4: List[Int] = List(4, 5)
scala> val sit = xs sliding 3
sit: Iterator[List[Int]] = non-empty iterator
scala> sit.next()
res5: List[Int] = List(1, 2, 3)
scala> sit.next()
res6: List[Int] = List(2, 3, 4)
scala> sit.next()
res7: List[Int] = List(3, 4, 5)
當只有一個迭代器可用時,Trait Iterable增加了一些其他方法,為了能被有效的實現的可遍歷的情況。這些方法總結在下面的表中。
Trait Iterable操作
WHAT IT IS | WHAT IT DOES |
---|---|
抽象方法: | |
xs.iterator | xs迭代器生成的每一個元素,以相同的順序就像foreach一樣遍歷元素。 |
其他迭代器: | |
xs grouped size | 一個迭代器生成一個固定大小的容器(collection)塊。 |
xs sliding size | 一個迭代器生成一個固定大小的滑動視窗作為容器(collection)的元素。 |
子容器(Subcollection): | |
xs takeRight n | 一個容器(collection)由xs的最後n個元素組成(或,若定義的元素是無序,則由任意的n個元素組成)。 |
xs dropRight n | 一個容器(collection)由除了xs 被取走的(執行過takeRight ()方法)n個元素外的其餘元素組成。 |
拉鍊方法(Zippers): | |
xs zip ys | 把一對容器 xs和ys的包含的元素合成到一個iterabale。 |
xs zipAll (ys, x, y) | 一對容器 xs 和ys的相應的元素合併到一個iterable ,實現方式是通過附加的元素x或y,把短的序列被延展到相對更長的一個上。 |
xs.zip WithIndex | 把一對容器xs和它的序列,所包含的元素組成一個iterable 。 |
比對: | |
xs sameElements ys | 測試 xs 和 ys 是否以相同的順序包含相同的元素。 |
在Iterable下的繼承層次結構你會發現有三個traits:Seq,Set,和 Map。這三個Traits有一個共同的特徵,它們都實現了PartialFunction trait以及它的應用和isDefinedAt 方法。然而,每一個trait實現的PartialFunction 方法卻各不相同。
例如序列,使用用的是位置索引,它裡面的元素的總是從0開始編號。即Seq(1, 2, 3)(1)
為2。例如sets,使用的是成員測試。例如Set('a', 'b', 'c')('b')
算出來的是true,而Set()('a')
為false。最後,maps使用的是選擇。比如Map('a' -> 1, 'b' -> 10, 'c' -> 100)('b')
得到的是10。
接下來,我們將詳細的介紹三種類型的容器(collection)。
序列trait:Seq、IndexedSeq及LinearSeq
Seq trait用於表示序列。所謂序列,指的是一類具有一定長度的可迭代訪問的物件,其中每個元素均帶有一個從0開始計數的固定索引位置。
序列的操作有以下幾種,如下表所示:
- 索引和長度的操作 apply、isDefinedAt、length、indices,及lengthCompare。序列的apply操作用於索引訪問;因此,Seq[T]型別的序列也是一個以單個Int(索引下標)為引數、返回值型別為T的偏函式。換言之,Seq[T]繼承自Partial Function[Int, T]。序列各元素的索引下標從0開始計數,最大索引下標為序列長度減一。序列的length方法是collection的size方法的別名。lengthCompare方法可以比較兩個序列的長度,即便其中一個序列長度無限也可以處理。
- 索引檢索操作(indexOf、lastIndexOf、indexofSlice、lastIndexOfSlice、indexWhere、lastIndexWhere、segmentLength、prefixLength)用於返回等於給定值或滿足某個謂詞的元素的索引。
- 加法運算(+:,:+,padTo)用於在序列的前面或者後面新增一個元素並作為新序列返回。
- 更新操作(updated,patch)用於替換原序列的某些元素並作為一個新序列返回。
- 排序操作(sorted, sortWith, sortBy)根據不同的條件對序列元素進行排序。
- 反轉操作(reverse, reverseIterator, reverseMap)用於將序列中的元素以相反的順序排列。
- 比較(startsWith, endsWith, contains, containsSlice, corresponds)用於對兩個序列進行比較,或者在序列中查詢某個元素。
- 多集操作(intersect, diff, union, distinct)用於對兩個序列中的元素進行類似集合的操作,或者刪除重複元素。
如果一個序列是可變的,它提供了另一種更新序列中的元素的,但有副作用的update方法,Scala中常有這樣的語法,如seq(idx) = elem。它只是seq.update(idx, elem)的簡寫,所以update 提供了方便的賦值語法。應注意update 和updated之間的差異。update 再原來基礎上更改序列中的元素,並且僅適用於可變序列。而updated 適用於所有的序列,它總是返回一個新序列,而不會修改原序列。
Set類的操作
WHAT IT IS | WHAT IT DOES |
---|---|
索引和長度 | |
xs(i) | (或者寫作xs apply i)。xs的第i個元素 |
xs isDefinedAt i | 測試xs.indices中是否包含i。 |
xs.length | 序列的長度(同size)。 |
xs.lengthCompare ys | 如果xs的長度小於ys的長度,則返回-1。如果xs的長度大於ys的長度,則返回+1,如果它們長度相等,則返回0。即使其中一個序列是無限的,也可以使用此方法。 |
xs.indices | xs的索引範圍,從0到xs.length - 1。 |
索引搜尋 | |
xs indexOf x | 返回序列xs中等於x的第一個元素的索引(存在多種變體)。 |
xs lastIndexOf x | 返回序列xs中等於x的最後一個元素的索引(存在多種變體)。 |
xs indexOfSlice ys | 查詢子序列ys,返回xs中匹配的第一個索引。 |
xs indexOfSlice ys | 查詢子序列ys,返回xs中匹配的倒數一個索引。 |
xs indexWhere p | xs序列中滿足p的第一個元素。(有多種形式) |
xs segmentLength (p, i) | xs中,從xs(i)開始並滿足條件p的元素的最長連續片段的長度。 |
xs prefixLength p | xs序列中滿足p條件的先頭元素的最大個數。 |
加法: | |
x +: xs | 由序列xs的前方新增x所得的新序列。 |
xs :+ x | 由序列xs的後方追加x所得的新序列。 |
xs padTo (len, x) | 在xs後方追加x,直到長度達到len後得到的序列。 |
更新 | |
xs patch (i, ys, r) | 將xs中第i個元素開始的r個元素,替換為ys所得的序列。 |
xs updated (i, x) | 將xs中第i個元素替換為x後所得的xs的副本。 |
xs(i) = x | (或寫作 xs.update(i, x),僅適用於可變序列)將xs序列中第i個元素修改為x。 |
排序 | |
xs.sorted | 通過使用xs中元素型別的標準順序,將xs元素進行排序後得到的新序列。 |
xs sortWith lt | 將lt作為比較操作,並以此將xs中的元素進行排序後得到的新序列。 |
xs sortBy f | 將序列xs的元素進行排序後得到的新序列。參與比較的兩個元素各自經f函式對映後得到一個結果,通過比較它們的結果來進行排序。 |
反轉 | |
xs.reverse | 與xs序列元素順序相反的一個新序列。 |
xs.reverseIterator | 產生序列xs中元素的反序迭代器。 |
xs reverseMap f | 以xs的相反順序,通過f對映xs序列中的元素得到的新序列。 |
比較 | |
xs startsWith ys | 測試序列xs是否以序列ys開頭(存在多種形式)。 |
xs endsWith ys | 測試序列xs是否以序列ys結束(存在多種形式)。 |
xs contains x | 測試xs序列中是否存在一個與x相等的元素。 |
xs containsSlice ys | 測試xs序列中是否存在一個與ys相同的連續子序列。 |
(xs corresponds ys)(p) | 測試序列xs與序列ys中對應的元素是否滿足二元的判斷式p。 |
多集操作 | |
xs intersect ys | 序列xs和ys的交集,並保留序列xs中的順序。 |
xs diff ys | 序列xs和ys的差集,並保留序列xs中的順序。 |
xs union ys | 並集;同xs ++ ys。 |
xs.distinct | 不含重複元素的xs的子序列。 |
特性(trait) Seq 具有兩個子特徵(subtrait) LinearSeq和IndexedSeq。它們不新增任何新的操作,但都提供不同的效能特點:線性序列具有高效的 head 和 tail 操作,而索引序列具有高效的apply, length, 和 (如果可變) update操作。
常用線性序列有 scala.collection.immutable.List
和scala.collection.immutable.Stream
。常用索引序列有scala.Array scala.collection.mutable.ArrayBuffer
。Vector 類提供一個在索引訪問和線性訪問之間有趣的折中。它同時具有高效的恆定時間的索引開銷,和恆定時間的線性訪問開銷。正因為如此,對於混合訪問模式,vector是一個很好的基礎。後面將詳細介紹vector。
緩衝器
Buffers是可變序列一個重要的種類。它們不僅允許更新現有的元素,而且允許元素的插入、移除和在buffer尾部高效地新增新元素。buffer 支援的主要新方法有:用於在尾部新增元素的 +=
和 ++=
;用於在前方新增元素的+=:
和++=:
;用於插入元素的 insert
和insertAll
;以及用於刪除元素的remove
和 -=
。如下表所示。
ListBuffer和ArrayBuffer是常用的buffer實現 。顧名思義,ListBuffer依賴列表(List),支援高效地將它的元素轉換成列表。而ArrayBuffer依賴陣列(Array),能快速地轉換成陣列。
Buffer類的操作
WHAT IT IS | WHAT IT DOES |
---|---|
加法: | |
buf += x | 將元素x追加到buffer,並將buf自身作為結果返回。 |
buf += (x, y, z) | 將給定的元素追加到buffer。 |
buf ++= xs | 將xs中的所有元素追加到buffer。 |
x +=: buf | 將元素x新增到buffer的前方。 |
xs ++=: buf | 將xs中的所有元素都新增到buffer的前方。 |
buf insert (i, x) | 將元素x插入到buffer中索引為i的位置。 |
buf insertAll (i, xs) | 將xs的所有元素都插入到buffer中索引為i的位置。 |
移除: | |
buf -= x | 將元素x從buffer中移除。 |
buf remove i | 將buffer中索引為i的元素移除。 |
buf remove (i, n) | 將buffer中從索引i開始的n個元素移除。 |
buf trimStart n | 移除buffer中的前n個元素。 |
buf trimEnd n | 移除buffer中的後n個元素。 |
buf.clear() | 移除buffer中的所有元素。 |
克隆: | |
buf.clone | 與buf具有相同元素的新buffer。 |
集合(Set)
集合是不包含重複元素的可迭代物件。下面的通用集合表和可變集合表中概括了集合型別適用的運算。分為幾類:
- 測試型的方法:contains,apply,subsetOf。contains方法用於判斷集合是否包含某元素。集合的apply方法和contains方法的作用相同,因此 set(elem) 等同於set constains elem。這意味著集合物件的名字能作為其自身是否包含某元素的測試函式。
例如
val fruit = Set("apple", "orange", "peach", "banana")
fruit: scala.collection.immutable.Set[java.lang.String] =
Set(apple, orange, peach, banana)
scala> fruit("peach")
res0: Boolean = true
scala> fruit("potato")
res1: Boolean = false
- 加法型別的方法: + 和 ++ 。新增一個或多個元素到集合中,產生一個新的集合。
- 減法型別的方法: - 、--。它們實現從一個集合中移除一個或多個元素,產生一個新的集合。
- Set運算包括並集、交集和差集。每一種運算都存在兩種書寫形式:字母和符號形式。字母形式:intersect、union和diff,符號形式:&、|和&~。事實上,Set中繼承自Traversable的++也能被看做union或|的另一個別名。區別是,++的引數為Traversable物件,而union和|的引數是集合。
Set 類的操作
WHAT IT IS | WHAT IT DOES |
---|---|
實驗程式碼: | |
xs contains x | 測試x是否是xs的元素。 |
xs(x) | 與xs contains x相同。 |
xs subsetOf ys | 測試xs是否是ys的子集。 |
加法: | |
xs + x | 包含xs中所有元素以及x的集合。 |
xs + (x, y, z) | 包含xs中所有元素及附加元素的集合 |
xs ++ ys | 包含xs中所有元素及ys中所有元素的集合 |
實驗程式碼: | |
xs - x | 包含xs中除x以外的所有元素的集合。 |
xs - x | 包含xs中除去給定元素以外的所有元素的集合。 |
xs -- ys | 集合內容為:xs中所有元素,去掉ys中所有元素後剩下的部分。 |
xs.empty | 與xs同類的空集合。 |
二進位制操作: | |
xs & ys | 集合xs和ys的交集。 |
xs intersect ys | 等同於 xs & ys。 |
xs | ys |
xs union ys | 等同於xs |
xs &~ ys | 集合xs和ys的差集。 |
xs diff ys | 等同於 xs &~ ys。 |
可變集合提供加法類方法,可以用來新增、刪除或更新元素。下面對這些方法做下總結。
mutable.Set 類的操作
WHAT IT IS | WHAT IT DOES |
---|---|
加法: | |
xs += x | 把元素x新增到集合xs中。該操作有副作用,它會返回左操作符,這裡是xs自身。 |
xs += (x, y, z) | 新增指定的元素到集合xs中,並返回xs本身。(同樣有副作用) |
xs ++= ys | 新增集合ys中的所有元素到集合xs中,並返回xs本身。(表示式有副作用) |
xs add x | 把元素x新增到集合xs中,如集合xs之前沒有包含x,該操作返回true,否則返回false。 |
移除: | |
xs -= x | 從集合xs中刪除元素x,並返回xs本身。(表示式有副作用) |
xs -= (x, y, z) | 從集合xs中刪除指定的元素,並返回xs本身。(表示式有副作用) |
xs --= ys | 從集合xs中刪除所有屬於集合ys的元素,並返回xs本身。(表示式有副作用) |
xs remove x | 從集合xs中刪除元素x。如之前xs中包含了x元素,返回true,否則返回false。 |
xs retain p | 只保留集合xs中滿足條件p的元素。 |
xs.clear() | 刪除集合xs中的所有元素。 |
**更新: ** | |
xs(x) = b | ( 同 xs.update(x, b) )引數b為布林型別,如果值為true就把元素x加入集合xs,否則從集合xs中刪除x。 |
克隆: | |
xs.clone | 產生一個與xs具有相同元素的可變集合。 |
與不變集合一樣,可變集合也提供了+
和++
操作符來新增元素,-
和--
用來刪除元素。但是這些操作在可變集合中通常很少使用,因為這些操作都要通過集合的拷貝來實現。可變集合提供了更有效率的更新方法,+=
和-=
。 s += elem
,新增元素elem到集合s中,並返回產生變化後的集合作為運算結果。同樣的,s -= elem
執行從集合s中刪除元素elem的操作,並返回產生變化後的集合作為運算結果。除了+=
和-=
之外還有從可遍歷物件集合或迭代器集合中新增和刪除所有元素的批量操作符++=
和--=
。
選用+=
和-=
這樣的方法名使得我們得以用非常近似的程式碼來處理可變集合和不可變集合。先看一下以下處理不可變集合s的REPL會話:
scala> var s = Set(1, 2, 3)
s: scala.collection.immutable.Set[Int] = Set(1, 2, 3)
scala> s += 4
scala> s -= 2
scala> s
res2: scala.collection.immutable.Set[Int] = Set(1, 3, 4)
我們在immutable.Set
型別的變數中使用+=
和-=
。諸如 s += 4
的表示式是 s = s + 4
的縮寫,它的作用是,在集合s上運用方法+
,並把結果賦回給變數s。下面我們來分析可變集合上的類似操作。
scala> val s = collection.mutable.Set(1, 2, 3)
s: scala.collection.mutable.Set[Int] = Set(1, 2, 3)
scala> s += 4
res3: s.type = Set(1, 4, 2, 3)
scala> s -= 2
res4: s.type = Set(1, 4, 3)
最後結果看起來和之前的在非可變集合上的操作非常相似;從Set(1, 2, 3)
開始,最後得到Set(1, 3, 4)
。然而,儘管相似,但它們在實現上其實是不同的。 這裡s += 4
是在可變集合值s上呼叫+=
方法,它會改變s的內容。同樣的,s -= 2
也是在s上呼叫 -=
方法,也會修改s集合的內容。
通過比較這兩種方式得出一個重要的原則。我們通常能用一個非可變集合的變數來替換可變集合的常量,反之亦然。這一原則至少在沒有別名的引用新增到Collection時起作用。別名引用主要是用來觀察操作在Collection上直接做的修改還是生成了一個新的Collection。
可變集合同樣提供作為+=
和-=
的變型方法,add和remove,它們的不同之處在於add和remove會返回一個表明運算是否對集合有作用的Boolean值
目前可變集合預設使用雜湊表來儲存集合元素,非可變集合則根據元素個數的不同,使用不同的方式來實現。空集用單例物件來表示。元素個數小於等於4的集合可以使用單例物件來表達,元素作為單例物件的欄位來儲存。 元素超過4個,非可變集合就用雜湊字首樹(hash trie)來實現。
採用這種表示方法,較小的不可變集合(元素數不超過4)往往會比可變集合更加緊湊和高效。所以,在處理小尺寸的集合時,不妨試試不可變集合。
集合的兩個特質是SortedSet和 BitSet。
有序集(SortedSet)
SortedSet 是指以特定的順序(這一順序可以在建立集合之初自由的選定)排列其元素(使用iterator或foreach)的集合。 SortedSet 的預設表示是有序二叉樹,即左子樹上的元素小於所有右子樹上的元素。這樣,一次簡單的順序遍歷能按增序返回集合中的所有元素。Scala的類 immutable.TreeSet
使用紅黑樹實現,它在維護元素順序的同時,也會保證二叉樹的平衡,即葉節點的深度差最多為1。
建立一個空的 TreeSet ,可以先定義排序規則:
scala> val myOrdering = Ordering.fromLessThan[String](/DOC_Scala/chinese_scala_offical_document/file/_ > _)
myOrdering: scala.math.Ordering[String] = ...
然後,用這一排序規則建立一個空的樹集:
scala> TreeSet.empty(myOrdering)
res1: scala.collection.immutable.TreeSet[String] = TreeSet()
或者,你也可以不指定排序規則引數,只需要給定一個元素型別或空集合。在這種情況下,將使用此元素型別預設的排序規則。
scala> TreeSet.empty[String]
res2: scala.collection.immutable.TreeSet[String] = TreeSet()
如果通過已有的TreeSet來建立新的集合(例如,通過串聯或過濾操作),這些集合將和原集合保持相同的排序規則。例如,
scala> res2 + ("one", "two", "three", "four")
res3: scala.collection.immutable.TreeSet[String] = TreeSet(four, one, three, two)
有序集合同樣支援元素的範圍操作。例如,range方法返回從指定起始位置到結束位置(不含結束元素)的所有元素,from方法返回大於等於某個元素的所有元素。呼叫這兩種方法的返回值依然是有序集合。例如:
scala> res3 range ("one", "two")
res4: scala.collection.immutable.TreeSet[String] = TreeSet(one, three)
scala> res3 from "three"
res5: scala.