本文 AI 產出,尚未審核

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 形式

最佳實踐

  1. 保持單行:若推導式超過 80 個字元,使用圓括號換行,提升可讀性。

    result = {
        x**2 for x in range(1, 101)
        if x % 3 == 0 and x % 5 != 0
    }
    
  2. 盡量使用不可變元素:若要把集合作為其他容器的鍵,先把推導式結果包裝成 frozenset

  3. 結合 mapfilter:在需要複雜變換時,可先用 map/filter 再套用集合推導式,保持語意清晰。

  4. 測試大資料:對於大型迭代物件,集合推導式的記憶體開銷仍與產生的集合大小成正比,必要時可改用生成器配合 set() 逐步累加。


實際應用場景

  1. 資料清理

    • 從 CSV 讀入的文字欄位可能包含重複值,使用 {v.strip().lower() for v in column} 立即得到唯一、正規化的集合。
  2. 快速交集檢查

    • 判斷兩個使用者的興趣是否有重疊:
      common = {tag for tag in user_a_tags if tag in user_b_tags}
      if common:
          print("有共同興趣:", common)
      
  3. 防止重複計算

    • 在圖形演算法中,記錄已拜訪的節點:
      visited = {start_node}
      frontier = {nbr for nbr in graph[start_node] if nbr not in visited}
      
  4. 建立索引

    • 文字搜尋系統常把每篇文件的關鍵字集合化,快速比對文件是否包含特定關鍵字。
  5. 安全性檢查

    • 只允許白名單內的檔案副檔名上傳:
      allowed = {'.png', '.jpg', '.gif'}
      if Path(filename).suffix.lower() in allowed:
          # 處理上傳
      

總結

集合推導式是 Python 資料結構中 簡潔、效能兼具 的利器。透過單行語法,我們可以同時完成 遍歷、過濾、轉換,且自動去除重複,讓程式碼更易讀、維護成本更低。掌握以下要點,即可在日常開發中發揮最大效益:

  • 使用 {} 包裹推導式,確保產生的是集合。
  • 只在 不可變 物件上做為集合元素,避免 unhashable 錯誤。
  • 針對複雜條件或多層迭代,適度換行或拆解成函式。
  • 結合 frozensetenumerateitertools 等工具,提升彈性與可重用性。

透過本篇的範例與實務情境,你應該已經能夠 自信地在程式中使用集合推導式,讓資料處理變得更快速、更可靠。祝你寫程式愉快,持續探索 Python 的無限可能!