集合

集合

語言

Set 是不包含重複元素的 Iterable。一般集合、不可變集合和可變集合的集合操作摘要如下表所示。它們屬於以下類別

  • 測試 containsapplysubsetOfcontains 方法會詢問集合是否包含給定的元素。apply 方法對集合來說與 contains 相同,因此 set(elem)set contains elem 相同。這表示集合也可以用作測試函式,為其包含的元素傳回 true。

例如

scala> 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
  • 新增 inclconcat (或分別為 +++),將一個或多個元素新增至集合,產生新的集合。
  • 移除 exclremovedAll (或分別為 ---),將一個或多個元素從集合中移除,產生新的集合。
  • 集合運算用於聯集、交集和集合差集。每個運算有兩種形式:字母和符號。字母版本為 intersectuniondiff,而符號版本為 &|&~。事實上,SetIterable 繼承的 ++ 可以視為 union| 的另一個別名,但 ++ 使用 IterableOnce 參數,而 union| 使用集合。

Set 類別中的運算

其為 其作用
測試  
xs 包含 x 測試 x 是否為 xs 的元素。
xs(x) xs contains x 相同。
xs 是 ys 的子集 測試 xs 是否是 ys 的子集。
加法  
xs 連接 ys
xs ++ ys
包含 xs 的所有元素以及 ys 的所有元素的集合。
移除  
xs.empty xs 相同類型的空集合。
二元運算  
xs 與 ys 的交集
xs & ys
xsys 的集合交集。
xs 與 ys 的聯集
xs | ys
xsys 的集合聯集。
xs 與 ys 的差集
xs &~ ys
xsys 的集合差集。

不可變集合提供方法來新增或移除元素,方法是傳回新的 Set,如下所述。

immutable.Set 類別中的運算

其為 其作用
加法  
xs 包含 x
xs + x
包含 xs 的所有元素以及 x 的集合。
移除  
xs 排除 x
xs - x
包含 xs 中所有元素的集合,但 x 除外。
xs removedAll ys
xs -- ys
包含 xs 中所有元素的集合,但 ys 的元素除外。

可變集合提供其他方法來新增、移除或更新元素,如下所述。

mutable.Set 類別中的操作

其為 其作用
加法  
xs addOne x
xs += x
將元素 x 新增到集合 xs 中,作為副作用並傳回 xs 本身。
xs addAll ys
xs ++= ys
ys 中的所有元素新增到集合 xs 中,作為副作用並傳回 xs 本身。
xs add x 將元素 x 新增到 xs 中,如果 x 之前未包含在集合中,則傳回 true;如果已包含,則傳回 false
移除  
xs subtractOne x
xs -= x
從集合 xs 中移除元素 x,作為副作用並傳回 xs 本身。
xs subtractAll ys
xs --= ys
從集合 xs 中移除 ys 中的所有元素,作為副作用並傳回 xs 本身。
xs 移除 x xs 中移除元素 x,如果 x 之前包含在集合中,則傳回 true,如果沒有,則傳回 false
xs filterInPlace p 僅保留 xs 中符合謂詞 p 的元素。
xs.clear() xs 中移除所有元素。
更新  
xs(x) = b (或寫成 xs.update(x, b))。如果布林參數 btrue,則將 x 加入 xs,否則從 xs 中移除 x
複製  
xs.clone 一個新的可變集合,與 xs 具有相同的元素。

運算 s += elemelem 加入集合 s 作為副作用,並傳回變異的集合作為結果。同樣地,s -= elem 從集合中移除 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 類型的 var 上使用 +=-=。像 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 -= 2 現在會在同一個集合上呼叫 -= 方法。

比較這兩個互動顯示了一個重要的原則。您通常可以用儲存在 val 中的可變集合取代儲存在 var 中的不可變集合,反之亦然。至少在沒有透過集合的別名參考來觀察集合是就地更新還是建立新集合時,這項原則才有效。

可變集合也提供 add 和 remove 作為 +=-= 的變體。不同之處在於 addremove 會傳回布林結果,表示操作是否對集合產生影響。

可變集合的目前預設實作使用雜湊表來儲存集合的元素。不可變集合的預設實作使用適應集合元素數量的表示法。空集合僅由單一物件表示。大小最多為 4 的集合由單一物件表示,該物件將所有元素儲存在欄位中。超過該大小後,不可變集合會實作為 壓縮雜湊陣列對應前綴樹

這些表示法選擇的結果是,對於小型的集合(假設最多 4 個),不可變集合通常比可變集合更精簡且更有效率。因此,如果您預期集合的大小會很小,請嘗試讓它不可變。

集合的兩個子特質是 SortedSetBitSet

已排序集合

一個 SortedSet 是一個集合,它會以給定的順序產生其元素(使用 iteratorforeach),而這個順序可以在建立集合時自由選擇。 SortedSet 的預設表示法是一個有序的二元樹,它維持一個不變式,即節點的左子樹中的所有元素都比右子樹中的所有元素小。這樣,一個簡單的順序遍歷就可以按遞增順序返回所有樹元素。Scala 的類別 immutable.TreeSet 使用一個紅黑樹實作來維持這個順序不變式,同時保持樹的平衡,也就是說,從樹的根節點到葉節點的所有路徑的長度只相差最多一個元素。

若要建立一個空的 TreeSet,你可以先指定想要的順序

scala> val myOrdering = Ordering.fromLessThan[String](_ > _)
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()

如果你從一個樹集建立新的集合(例如透過串接或篩選),它們將保持與原始集合相同的順序。例如,

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 rangeFrom "three"
res5: scala.collection.immutable.TreeSet[String] = TreeSet(three, two)

位元集

位元集是非負整數元素的集合,這些元素會實作在一個或多個封裝位元的字元中。 BitSet 的內部表示法會使用 Long 陣列。第一個 Long 涵蓋元素 0 到 63,第二個涵蓋 64 到 127,以此類推(0 到 127 範圍內元素的不變位元集會最佳化陣列,並將位元直接儲存在一個或兩個 Long 欄位中)。對於每個 Long,如果集合中包含對應元素,其 64 個位元中的每個位元都會設定為 1,否則會取消設定。因此,位元集的大小取決於儲存在其中的最大整數。如果 N 是最大整數,則集合的大小為 N/64 Long 字元,或 N/8 位元組,加上少數額外的狀態資訊位元組。

因此,如果位元集包含許多小元素,則位元集會比其他集合更精簡。位元集的另一個優點是,使用 contains 進行成員測試,或使用 +=-= 進行元素新增和移除等作業都非常有效率。

此頁面的貢獻者