本文 AI 產出,尚未審核
Python 資料結構 – 集合(set, frozenset)
主題:集合操作(交集、聯集、差集)
簡介
在日常開發中,我們常常需要 快速判斷兩組資料之間的關係,例如「哪些使用者同時出現在兩個活動名單」或「哪些商品在庫存中已經售罄」。
Python 的 set(可變集合)與 frozenset(不可變集合)提供了 O(1) 的成員測試與高效的集合運算,讓這類問題的解決變得既簡潔又具可讀性。
本單元將聚焦於 集合的三大基本操作:交集(intersection)、聯集(union)與 差集(difference)。掌握這些操作,您不僅能寫出更短的程式碼,還能提升演算法的效能,對於資料清理、權限管理、圖形演算法等領域都有直接的幫助。
核心概念
1. 什麼是 set 與 frozenset
| 類型 | 可變性 | 常見用途 |
|---|---|---|
set |
可變(可增刪元素) | 動態資料過濾、去除重複 |
frozenset |
不可變(hashable) | 作為字典或集合的鍵、快取結果 |
註:集合內的元素必須是 可雜湊(hashable) 的,例如
int、str、tuple(內部只含可雜湊元素),而list、dict則不可直接放入集合。
# 建立 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會自動去除重複,若希望保留重複次數,請改用list或collections.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() 回去。 |
| 元素不可雜湊 | 嘗試把 list、dict 放入集合會失敗。 |
使用 tuple 或自行實作 __hash__ 方法,使元素可雜湊。 |
**誤用 &、` |
、-` 產生布林運算** |
在布林值上使用 &、` |
| 大型集合的多次運算 | 連續呼叫 union、intersection 會產生多個中間集合,浪費記憶體。 |
盡量使用 一次性 方法,例如 A.intersection(B, C, D),或把較大的集合放在最後。 |
不必要的 list() 轉換 |
把集合轉成列表再做 in 檢查,會失去 O(1) 的優勢。 |
直接使用集合的成員測試:if x in my_set:。 |
最佳實踐建議:
- 先判斷集合大小,把較小的集合放在左側執行交集或差集,可降低迭代成本。
- 若集合內容不會變動,優先使用
frozenset,讓它可以成為字典鍵或快取的唯一標識。 - 使用 集合推導式 (
{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 合併多個使用者的喜好集合 |
把多位使用者的商品集合合併,得到更廣的候選集。 |
| 文字分析 | intersection、symmetric_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 中最具威力的資料結構之一,透過 set 與 frozenset 我們可以在 常數時間 完成成員測試、去除重複、以及 交集、聯集、差集 等集合運算。
本文重點回顧:
- 交集 (
&、.intersection) 用於找出共同元素。 - 聯集 (
|、.union) 用於合併多個集合,自動去除重複。 - 差集 (
-、.difference) 用於剔除不需要的元素。 frozenset為不可變集合,適合當作字典鍵或快取標識。- 注意元素的可雜湊性、集合大小的先後順序,以及避免不必要的型別轉換。
掌握這些操作後,您將能在 資料清理、權限驗證、演算法優化 等多個實務領域中寫出 更簡潔、更高效 的程式碼。祝您在 Python 的資料結構之路上越走越遠!