本文 AI 產出,尚未審核

Python 資料結構 – 集合(set, frozenset)

主題:集合操作(交集、聯集、差集)


簡介

在日常開發中,我們常常需要 快速判斷兩組資料之間的關係,例如「哪些使用者同時出現在兩個活動名單」或「哪些商品在庫存中已經售罄」。
Python 的 set(可變集合)與 frozenset(不可變集合)提供了 O(1) 的成員測試與高效的集合運算,讓這類問題的解決變得既簡潔又具可讀性。

本單元將聚焦於 集合的三大基本操作交集(intersection)、聯集(union)與 差集(difference)。掌握這些操作,您不僅能寫出更短的程式碼,還能提升演算法的效能,對於資料清理、權限管理、圖形演算法等領域都有直接的幫助。


核心概念

1. 什麼是 setfrozenset

類型 可變性 常見用途
set 可變(可增刪元素) 動態資料過濾、去除重複
frozenset 不可變(hashable) 作為字典或集合的鍵、快取結果

:集合內的元素必須是 可雜湊(hashable) 的,例如 intstrtuple(內部只含可雜湊元素),而 listdict 則不可直接放入集合。

# 建立 set 與 frozenset
numbers = {1, 2, 3, 4}          # set
immutable = frozenset([3, 4, 5]) # frozenset
print(numbers, immutable)       # {1, 2, 3, 4} frozenset({3, 4, 5})

2. 交集(Intersection)

交集返回同時出現在兩個(或多個)集合中的元素。

2.1 方法與運算子

方法 語法 說明
set.intersection(*others) A.intersection(B, C) 回傳新集合,不改變原集合
&(位元運算子) A & B 同上,語法更簡潔
# 交集範例
a = {1, 2, 3, 4}
b = {3, 4, 5, 6}
c = {4, 7, 8}

# 方法呼叫
inter1 = a.intersection(b, c)   # {4}
# 運算子寫法
inter2 = a & b & c              # {4}
print(inter1, inter2)

小技巧:若集合大小相差懸殊,先把 較小的集合放在左側,可減少內部迭代次數。


3. 聯集(Union)

聯集把所有元素合併成一個集合,重複元素自動去除。

3.1 方法與運算子

方法 語法 說明
set.union(*others) A.union(B, C) 回傳新集合
` `(位元運算子) `A
# 聯集範例
x = {1, 2, 3}
y = {3, 4, 5}
z = {5, 6}

# 方法呼叫
union1 = x.union(y, z)   # {1, 2, 3, 4, 5, 6}
# 運算子寫法
union2 = x | y | z       # {1, 2, 3, 4, 5, 6}
print(union1, union2)

實務提醒set 會自動去除重複,若希望保留重複次數,請改用 listcollections.Counter


4. 差集(Difference)

差集返回屬於第一個集合但 不在 其他集合中的元素。

4.1 方法與運算子

方法 語法 說明
set.difference(*others) A.difference(B, C) 回傳新集合
-(減號) A - B 同上
# 差集範例
students = {"alice", "bob", "carol", "dave"}
absent   = {"bob", "dave"}

# 方法呼叫
present = students.difference(absent)   # {"alice", "carol"}
# 運算子寫法
present2 = students - absent            # {"alice", "carol"}
print(present, present2)

延伸:若想取得 對稱差集(只出現在其中一個集合),可使用 symmetric_difference()^ 運算子。

a = {1, 2, 3}
b = {3, 4, 5}
print(a ^ b)               # {1, 2, 4, 5}

常見陷阱與最佳實踐

陷阱 說明 解決方式
混用可變與不可變集合 frozenset 當作 set 直接修改會拋出 AttributeError 需要變更時,先 set(frozenset_obj) 轉為可變集合,修改完再 frozenset() 回去。
元素不可雜湊 嘗試把 listdict 放入集合會失敗。 使用 tuple 或自行實作 __hash__ 方法,使元素可雜湊。
**誤用 &、` -` 產生布林運算** 在布林值上使用 &、`
大型集合的多次運算 連續呼叫 unionintersection 會產生多個中間集合,浪費記憶體。 盡量使用 一次性 方法,例如 A.intersection(B, C, D),或把較大的集合放在最後。
不必要的 list() 轉換 把集合轉成列表再做 in 檢查,會失去 O(1) 的優勢。 直接使用集合的成員測試:if x in my_set:

最佳實踐建議

  1. 先判斷集合大小,把較小的集合放在左側執行交集或差集,可降低迭代成本。
  2. 若集合內容不會變動,優先使用 frozenset,讓它可以成為字典鍵或快取的唯一標識。
  3. 使用 集合推導式 ({expr for x in iterable if condition}) 產生新集合,語法簡潔且效能佳。
# 集合推導式範例:過濾奇數
nums = {1, 2, 3, 4, 5, 6}
odd = {n for n in nums if n % 2 == 1}
print(odd)   # {1, 3, 5}

實際應用場景

場景 使用的集合操作 為何適合
權限管理 intersection 判斷使用者是否同時具備多項權限 只要有任一缺失即可拒絕,集合交集直接返回共同權限。
資料清理 difference 找出資料庫中已刪除的 ID 快速算出「舊資料」與「新資料」的差異,避免逐筆比對。
推薦系統 union 合併多個使用者的喜好集合 把多位使用者的商品集合合併,得到更廣的候選集。
文字分析 intersectionsymmetric_difference 比較兩篇文章的關鍵詞 交集找共同關鍵詞,對稱差集找獨特詞彙,用於相似度計算。
快取鍵值 frozenset 作為字典的鍵 把「參數集合」轉為不可變集合,可直接作為快取字典的鍵,避免自行拼字串。
# 快取範例:把參數集合當作鍵
cache = {}
def heavy_compute(a, b, c):
    key = frozenset((a, b, c))
    if key in cache:
        return cache[key]
    # 假設這裡有耗時運算
    result = a + b * c
    cache[key] = result
    return result

print(heavy_compute(1, 2, 3))   # 計算
print(heavy_compute(3, 2, 1))   # 重新計算,因為集合不同
print(heavy_compute(1, 2, 3))   # 從快取取得

總結

集合是 Python 中最具威力的資料結構之一,透過 setfrozenset 我們可以在 常數時間 完成成員測試、去除重複、以及 交集、聯集、差集 等集合運算。
本文重點回顧:

  • 交集 (&.intersection) 用於找出共同元素。
  • 聯集 (|.union) 用於合併多個集合,自動去除重複。
  • 差集 (-.difference) 用於剔除不需要的元素。
  • frozenset 為不可變集合,適合當作字典鍵或快取標識。
  • 注意元素的可雜湊性、集合大小的先後順序,以及避免不必要的型別轉換。

掌握這些操作後,您將能在 資料清理、權限驗證、演算法優化 等多個實務領域中寫出 更簡潔、更高效 的程式碼。祝您在 Python 的資料結構之路上越走越遠!