Python 資料結構 – 集合(set, frozenset)
主題:集合推導式(Set Comprehension)
簡介
在 Python 中,**集合(set)**是一種無序且不允許重複元素的容器。它在去除重複值、集合運算(交集、聯集、差集)以及快速成員測試等情境下非常好用。
隨著程式語言的演進,Python 提供了 推導式(comprehension) 這項語法糖,讓我們能以簡潔、可讀的方式一次產生列表、字典、集合或生成器。集合推導式(set comprehension)結合了集合的特性與推導式的簡潔,讓資料過濾、轉換的程式碼量大幅縮減,同時保留集合固有的去重與 O(1) 成員檢查效能,對初學者與中級開發者都是提升程式品質的關鍵工具。
本篇文章將深入探討集合推導式的語法、實作方式、常見陷阱與最佳實踐,並提供多個實務範例,幫助你在日常開發中快速上手、寫出更乾淨、更高效的程式碼。
核心概念
什麼是集合推導式?
集合推導式的基本語法與列表推導式相似,只是外層的方括號 [] 換成了大括號 {}:
{ expression for item in iterable if condition }
- expression:產生集合元素的運算式,可直接使用
item或對其做轉換。 - for item in iterable:遍歷任意可迭代物件(list、tuple、range、dict 等)。
- if condition(可選):篩選符合條件的元素,未通過的會被直接捨棄。
因為集合本身不允許重複,若 expression 產生相同的值,最終集合只會保留一個。
為什麼要使用集合推導式?
| 列表推導式 | 集合推導式 | 何時選擇 |
|---|---|---|
[] |
{} |
需要 去除重複、或 快速成員查詢 時 |
| 產生有序序列 | 產生 無序且唯一 的集合 | 只關心「有哪些」而非「順序」時 |
基本範例
1. 產生 1~10 的平方集合
squares = {x**2 for x in range(1, 11)}
print(squares) # {64, 1, 36, 4, 9, 16, 25, 49, 81, 100}
說明:
range(1, 11)產生 1 到 10,x**2為表達式。結果是一組不重複的平方數。
2. 篩選偶數後再取立方
cubes_of_even = {x**3 for x in range(1, 21) if x % 2 == 0}
print(cubes_of_even) # {8, 64, 216, 512, 1000, 1728, 2744, 4096, 5832, 8000}
說明:
if x % 2 == 0只保留偶數,最後得到偶數的立方集合。
3. 從字串列表中抽取唯一字母
words = ["apple", "banana", "cherry"]
unique_letters = {ch for word in words for ch in word}
print(sorted(unique_letters))
# ['a', 'b', 'c', 'e', 'h', 'l', 'n', 'p', 'r', 'y']
說明:使用兩層
for,先遍歷單字,再遍歷字母,最終得到所有不重複的字母。
4. 結合 enumerate 產生索引集合
fruits = ["orange", "grape", "lemon"]
idx_set = {i for i, fruit in enumerate(fruits) if fruit.startswith('g')}
print(idx_set) # {1}
說明:只保留以
g開頭的水果索引,結果是一個只包含1的集合。
5. 轉換 frozenset(不可變集合)時的技巧
numbers = [1, 2, 2, 3, 4, 4, 5]
immutable_set = frozenset({n for n in numbers if n % 2 == 1})
print(immutable_set) # frozenset({1, 3, 5})
說明:先用集合推導式過濾奇數,再以
frozenset()包裝成不可變集合,適合作為字典的鍵或其他需要 hash 的情境。
常見陷阱與最佳實踐
| 陷阱 | 可能的結果 | 解決方式 |
|---|---|---|
忘記加大括號,寫成 [] → 產生列表而非集合 |
失去去重特性、記憶體使用較高 | 確認外層使用 {} |
推導式內部產生可變物件(如 list、dict) → TypeError: unhashable type |
無法加入集合 | 使用不可變類型(tuple、frozenset)或改用列表推導式 |
| 過濾條件寫錯,導致所有元素都被排除 | 產生空集合 {} |
在開發時先印出條件測試結果 |
| 過度嵌套,可讀性下降 | 程式碼難以維護 | 若需要超過兩層 for,考慮拆成函式或使用 itertools.chain |
| 集合推導式與字典推導式寫法相近,易混淆 | 產生錯誤的資料結構 | 記住字典推導式使用 key: value 形式 |
最佳實踐
保持單行:若推導式超過 80 個字元,使用圓括號換行,提升可讀性。
result = { x**2 for x in range(1, 101) if x % 3 == 0 and x % 5 != 0 }盡量使用不可變元素:若要把集合作為其他容器的鍵,先把推導式結果包裝成
frozenset。結合
map、filter:在需要複雜變換時,可先用map/filter再套用集合推導式,保持語意清晰。測試大資料:對於大型迭代物件,集合推導式的記憶體開銷仍與產生的集合大小成正比,必要時可改用生成器配合
set()逐步累加。
實際應用場景
資料清理
- 從 CSV 讀入的文字欄位可能包含重複值,使用
{v.strip().lower() for v in column}立即得到唯一、正規化的集合。
- 從 CSV 讀入的文字欄位可能包含重複值,使用
快速交集檢查
- 判斷兩個使用者的興趣是否有重疊:
common = {tag for tag in user_a_tags if tag in user_b_tags} if common: print("有共同興趣:", common)
- 判斷兩個使用者的興趣是否有重疊:
防止重複計算
- 在圖形演算法中,記錄已拜訪的節點:
visited = {start_node} frontier = {nbr for nbr in graph[start_node] if nbr not in visited}
- 在圖形演算法中,記錄已拜訪的節點:
建立索引
- 文字搜尋系統常把每篇文件的關鍵字集合化,快速比對文件是否包含特定關鍵字。
安全性檢查
- 只允許白名單內的檔案副檔名上傳:
allowed = {'.png', '.jpg', '.gif'} if Path(filename).suffix.lower() in allowed: # 處理上傳
- 只允許白名單內的檔案副檔名上傳:
總結
集合推導式是 Python 資料結構中 簡潔、效能兼具 的利器。透過單行語法,我們可以同時完成 遍歷、過濾、轉換,且自動去除重複,讓程式碼更易讀、維護成本更低。掌握以下要點,即可在日常開發中發揮最大效益:
- 使用
{}包裹推導式,確保產生的是集合。 - 只在 不可變 物件上做為集合元素,避免
unhashable錯誤。 - 針對複雜條件或多層迭代,適度換行或拆解成函式。
- 結合
frozenset、enumerate、itertools等工具,提升彈性與可重用性。
透過本篇的範例與實務情境,你應該已經能夠 自信地在程式中使用集合推導式,讓資料處理變得更快速、更可靠。祝你寫程式愉快,持續探索 Python 的無限可能!