1. 集合數據結構概述

1.1 什麼是集合?

Python 中的集合(set)是一種無序、可變、不允許重複元素的數據結構,基於數學集合概念。集合的主要特點包括:

  • 無序性:元素沒有固定索引,無法通過位置訪問。
  • 唯一性:自動去除重複元素。
  • 可變性set 支持添加、刪除元素;frozenset 是不可變版本。
  • 高效性:基於哈希表,成員測試和集合運算複雜度為 O(1) 或 O(min(len(s1), len(s2)))。

定義示例

# 可變集合
s = {1, 2, 3}
# 不可變集合
fs = frozenset([1, 2, 3])
# 空集合(不能用 {},那是空字典)
empty_set = set()

1.2 集合的特性

  • 元素限制:集合元素必須是不可變類型(如整數、浮點數、字符串、元組),不能包含列表、字典或可變集合。
s = {1, "hello", (1, 2)}  # 合法
s = {1, [1, 2]}  # TypeError: unhashable type: 'list'
  • 哈希表實現:提供高效的成員測試和去重操作。
  • 內置支持:無需額外模塊,直接使用。

1.3 集合的主要用途

  • 去重:從列表或其他序列中移除重複元素。
  • 成員測試:快速檢查元素是否存在。
  • 集合運算:支持並集、交集、差集、對稱差集等數學操作。
  • 數據處理:用於數據分析、數據庫查詢優化、算法設計。

1.4 相關規範

  • PEP 8:代碼風格指南,規範集合變量命名和代碼格式。
  • Python 文檔docs.python.org/3/library/stdtypes.html#set 提供詳細集合操作説明。
  • 類型註解(PEP 484):使用 typing.Set 和 typing.FrozenSet 提高代碼可讀性。

2. 集合的基本用法

2.1 創建集合

集合可以通過多種方式創建:

  • 字面量
s = {1, 2, 3, 3}  # 自動去重:{1, 2, 3}
  • set() 構造函數
s = set([1, 2, 3, 3])  # 從列表創建:{1, 2, 3}
s = set("hello")  # 從字符串創建:{'h', 'e', 'l', 'o'}
  • frozenset() 構造函數
fs = frozenset([1, 2, 3])  # 不可變集合

注意

  • 空集合必須用 set(),因為 {} 表示空字典。
  • 集合元素必須可哈希(不可變類型)。

2.2 基本操作

  • 添加元素(僅限 set):
s = {1, 2}
s.add(3)  # {1, 2, 3}
s.add(2)  # 無變化(重複元素被忽略)
  • 移除元素(僅限 set):
s.remove(2)  # {1, 3},若元素不存在拋出 KeyError
s.discard(4)  # 無錯誤,即使 4 不存在
s.pop()  # 隨機移除並返回一個元素
s.clear()  # 清空集合:set()
  • 成員測試
>>> 1 in s
True
>>> 4 not in s
True
  • 長度和迭代
>>> len({1, 2, 3})
3
>>> for x in {1, 2, 3}:
...     print(x)
1
2
3

2.3 集合運算

Python 集合支持標準的數學集合運算:

  • 並集(union, |):合併兩個集合的元素。
a = {1, 2, 3}
b = {3, 4, 5}
>>> a | b
{1, 2, 3, 4, 5}
>>> a.union(b)
{1, 2, 3, 4, 5}
  • 交集(intersection, &):取兩個集合的公共元素。
>>> a & b
{3}
>>> a.intersection(b)
{3}
  • 差集(difference, -):取 a 中不在 b 的元素。
>>> a - b
{1, 2}
>>> a.difference(b)
{1, 2}
  • 對稱差集(symmetric_difference, ^):取僅在一個集合中的元素。
>>> a ^ b
{1, 2, 4, 5}
>>> a.symmetric_difference(b)
{1, 2, 4, 5}
  • 子集/超集
>>> {1, 2}.issubset(a)
True
>>> a.issuperset({1, 2})
True

注意

  • 運算符(如 |)和方法(如 union())功能相同,但方法支持多集合操作:
>>> a.union(b, {5, 6})
{1, 2, 3, 4, 5, 6}

2.4 frozenset 的特殊性

  • 不可變:無法添加/移除元素。
  • 可作為字典鍵或集合元素
fs = frozenset([1, 2])
d = {fs: "value"}  # 合法
s = {fs}  # 合法

示例

fs1 = frozenset([1, 2])
fs2 = frozenset([2, 3])
print(fs1 | fs2)  # frozenset({1, 2, 3})

3. 高級集合操作與技巧

3.1 集合推導式

類似列表推導式,集合推導式創建新集合:

# 生成 0-9 的平方集合
squares = {x**2 for x in range(10)}
print(squares)  # {0, 1, 4, 9, 16, 25, 36, 49, 64, 81}

技巧

  • 結合條件過濾:
even_squares = {x**2 for x in range(10) if x % 2 == 0}
print(even_squares)  # {0, 4, 16, 36, 64}
  • 去重複雜數據:
data = [("a", 1), ("b", 2), ("a", 1)]
unique = {tuple(item) for item in data}
print(unique)  # {('a', 1), ('b', 2)}

3.2 批量更新(僅限 set

  • update() / |=:添加多個元素。
s = {1, 2}
s.update([3, 4])  # 或 s |= {3, 4}
print(s)  # {1, 2, 3, 4}
  • intersection_update() / &=:保留交集。
s = {1, 2, 3}
s.intersection_update({2, 3, 4})  # 或 s &= {2, 3, 4}
print(s)  # {2, 3}
  • difference_update() / -=:移除差集。
s = {1, 2, 3}
s.difference_update({2})  # 或 s -= {2}
print(s)  # {1, 3}

技巧

  • 批量操作效率高於逐個添加/移除。
  • 使用 |= 等運算符更簡潔。

3.3 集合與列表的轉換

  • 去重:列表轉集合再轉回列表。
lst = [1, 2, 2, 3]
unique = list(set(lst))
print(unique)  # [1, 2, 3]
  • 排序:集合無序,需轉列表排序。
s = {3, 1, 2}
sorted_list = sorted(s)
print(sorted_list)  # [1, 2, 3]

技巧

  • 去重後保持順序(3.7+,字典保持插入順序):
lst = [1, 2, 2, 3, 1]
unique = list(dict.fromkeys(lst))
print(unique)  # [1, 2, 3]

3.4 性能優化

集合的哈希表實現使其在特定操作中效率極高:

  • 成員測試:O(1) vs 列表的 O(n)。
import timeit
print(timeit.timeit("1000 in s", setup="s = set(range(10000))"))  # 0.05
print(timeit.timeit("1000 in lst", setup="lst = list(range(10000))"))  # 5.0
  • 去重:集合優於循環檢查。
# 差:低效
lst = [1, 2, 2, 3]
unique = []
for x in lst:
    if x not in unique:
        unique.append(x)
# 好:高效
unique = list(set(lst))

技巧

  • 大數據集去重和查找優先使用集合。
  • 小數據集(<100 元素)可用列表,減少轉換開銷。

3.5 類型註解

使用 typing 模塊為集合添加類型提示:

from typing import Set
def unique_numbers(numbers: list[int]) -> Set[int]:
    return set(numbers)

frozenset 示例

from typing import FrozenSet
def get_fixed_set() -> FrozenSet[int]:
    return frozenset([1, 2, 3])

技巧

  • 結合 Mypy 檢查類型:
pip install mypy
mypy myfile.py

4. 集合操作的實際應用場景

4.1 數據去重

場景:從用户輸入中移除重複項。

# 輸入:用户提交的標籤列表
tags = ["python", "coding", "python", "data"]
unique_tags = set(tags)
print(unique_tags)  # {'python', 'coding', 'data'}

技巧

  • 保持順序(3.7+):
unique_tags = list(dict.fromkeys(tags))
print(unique_tags)  # ['python', 'coding', 'data']

4.2 數據庫查詢優化

場景:查找兩個表中的公共記錄。

# 模擬用户 ID 列表
table1 = {101, 102, 103, 104}
table2 = {103, 104, 105, 106}
common_ids = table1 & table2
print(common_ids)  # {103, 104}

技巧

  • 使用交集快速篩選公共數據。
  • 結合 SQLAlchemy 或 Pandas 優化數據庫操作:
import pandas as pd
df1 = pd.DataFrame({"id": [101, 102, 103]})
df2 = pd.DataFrame({"id": [103, 104, 105]})
common = set(df1["id"]) & set(df2["id"])

4.3 權限管理

場景:檢查用户權限。

user_permissions = {"read", "write"}
required_permissions = {"read", "execute"}
if required_permissions.issubset(user_permissions):
    print("Access granted")
else:
    print("Missing permissions:", required_permissions - user_permissions)
# 輸出:Missing permissions: {'execute'}

技巧

  • 使用 issubset() 簡化權限檢查。
  • 存儲權限為 frozenset,防止意外修改。

4.4 算法設計

場景:實現並查集(Union-Find)算法。

def find(parent: dict, x: int) -> int:
    """Find the root of element x."""
    if parent[x] != x:
        parent[x] = find(parent, parent[x])  # 路徑壓縮
    return parent[x]

def union(parent: dict, x: int, y: int) -> None:
    """Merge sets containing x and y."""
    parent[find(parent, x)] = find(parent, y)

# 示例:連接節點
nodes = {1, 2, 3, 4}
parent = {i: i for i in nodes}  # 每個節點初始化為自身父節點
union(parent, 1, 2)
union(parent, 3, 4)
print(find(parent, 1) == find(parent, 2))  # True

技巧

  • 集合適合存儲節點或分組。
  • 使用 frozenset 表示不可變分組。

4.5 遊戲開發(參考 TrafficFlowGame)

場景:管理遊戲中的障礙物集合。

# 障礙物座標(元組集合)
obstacles = {(10, 20), (30, 40), (10, 20)}
print(obstacles)  # {(10, 20), (30, 40)}
# 檢查玩家位置是否衝突
player_pos = (10, 20)
if player_pos in obstacles:
    print("Collision detected!")

技巧

  • 使用集合存儲座標,快速檢查碰撞。
  • 使用 frozenset 作為字典鍵存儲遊戲狀態。

5. 性能分析與優化

5.1 時間複雜度

集合的哈希表實現決定了其高效性:

  • 成員測試in):平均 O(1)。
  • 添加/移除addremovediscard):平均 O(1)。
  • 集合運算unionintersection 等):O(min(len(s1), len(s2)))。
  • 列表轉集合:O(n),n 為列表長度。

對比列表

import timeit
setup = """
lst = list(range(10000))
s = set(range(10000))
"""
print(timeit.timeit("9999 in lst", setup=setup))  # 慢:O(n)
print(timeit.timeit("9999 in s", setup=setup))  # 快:O(1)

5.2 內存佔用

  • 集合比列表佔用更多內存(哈希表開銷)。
  • frozenset 比 set 更省內存。
import sys
s = {1, 2, 3}
fs = frozenset([1, 2, 3])
print(sys.getsizeof(s))  # 更大
print(sys.getsizeof(fs))  # 更小

技巧

  • 小數據集用列表,減少哈希表開銷。
  • 不可變場景用 frozenset

5.3 優化策略

  • 批量操作:使用 update() 而非逐個 add()
# 差:低效
s = set()
for x in range(1000):
    s.add(x)
# 好:高效
s = set(range(1000))
  • 避免重複轉換:緩存集合結果。
# 差:重複創建
for _ in range(100):
    unique = set(lst)
# 好:緩存結果
unique = set(lst)
for _ in range(100):
    use(unique)

6. 工具支持與工作流優化

6.1 代碼檢查工具

  • flake8:檢查集合變量命名規範。
pip install flake8
flake8 myfile.py
  • pylint:確保集合操作符合規範。
pip install pylint
pylint myfile.py
  • Mypy:驗證集合類型註解。
from typing import Set
def process_set(s: Set[int]) -> Set[int]:
    return s

6.2 IDE 支持

  • VS Code
  • 插件:Python、Pylance(類型檢查)。
  • 自動補全集合方法。
  • PyCharm
  • 內置集合操作提示。
  • 檢查集合元素類型。
  • Jupyter Notebook
  • 交互式測試集合操作。
s = {1, 2, 3}
s | {4, 5}  # 單元格輸出:{1, 2, 3, 4, 5}

6.3 文檔生成

  • Sphinx:為集合操作函數生成文檔。
def union_sets(a: set, b: set) -> set:
    """Compute the union of two sets.

    :param a: First set
    :param b: Second set
    :returns: Union of the sets
    """
    return a | b
  • pydoc:生成文本文檔。
python -m pydoc mymodule

7. 常見問題與解決方案

7.1 不可哈希元素

  • 問題:嘗試添加列表到集合。
s = {1, [2, 3]}  # TypeError: unhashable type: 'list'
  • 解決:使用元組或 frozenset
s = {1, (2, 3)}  # 合法
s = {1, frozenset([2, 3])}  # 合法

7.2 無序性問題

  • 問題:集合元素順序不可控。
s = {3, 1, 2}
print(s)  # 可能輸出 {1, 2, 3} 或其他順序
  • 解決:轉列表排序。
sorted_list = sorted(s)
print(sorted_list)  # [1, 2, 3]

7.3 移除不存在元素

  • 問題remove() 拋出 KeyError。
s = {1, 2}
s.remove(3)  # KeyError
  • 解決:使用 discard()
s.discard(3)  # 無錯誤

7.4 內存佔用過高

  • 問題:大集合佔用內存。
  • 解決
  • 使用 frozenset 減少開銷。
  • 分批處理大數據集:
def process_large_data(data: list, chunk_size: int = 1000) -> set:
    result = set()
    for i in range(0, len(data), chunk_size):
        result.update(data[i:i + chunk_size])
    return result

8. 項目實踐:集合操作的應用

8.1 數據分析

場景:分析用户行為日誌,去重並提取公共行為。

# 日誌數據:用户點擊的頁面
user1 = {"home", "profile", "settings"}
user2 = {"home", "cart", "settings"}
# 公共頁面
common_pages = user1 & user2
print(common_pages)  # {'home', 'settings'}
# 所有頁面
all_pages = user1 | user2
print(all_pages)  # {'home', 'profile', 'settings', 'cart'}

技巧

  • 使用 Pandas 結合集合:
import pandas as pd
df = pd.DataFrame({"user1": list(user1), "user2": list(user2)})
unique_pages = set(df["user1"]) | set(df["user2"])

8.2 遊戲開發(參考 TrafficFlowGame)

場景:管理交通流量中的車輛集合。

# 車輛 ID 集合
highway1 = {101, 102, 103}
highway2 = {103, 104, 105}
# 檢測重複車輛
duplicate_vehicles = highway1 & highway2
print(duplicate_vehicles)  # {103}
# 新增車輛
highway1.add(106)

技巧

  • 使用 frozenset 存儲固定區域的車輛。
  • 結合集合檢查碰撞或重複。

8.3 API 數據去重

場景:處理 API 返回的重複數據。

import requests
# 模擬 API 返回的用户 ID
response = requests.get("https://api.example.com/users")
user_ids = [101, 102, 101, 103]
unique_ids = set(user_ids)
print(unique_ids)  # {101, 102, 103}

技巧

  • 緩存集合結果,避免重複去重。
  • 使用 frozenset 作為緩存鍵。

8.4 算法優化

場景:實現子集和問題。

from typing import Set
def subset_sum(numbers: list[int], target: int) -> Set[int]:
    """Find a subset that sums to target."""
    result = set()
    def backtrack(index: int, curr_sum: int, curr_set: set):
        if curr_sum == target:
            result.update(curr_set)
            return
        if curr_sum > target or index >= len(numbers):
            return
        # 包含當前元素
        backtrack(index + 1, curr_sum + numbers[index], curr_set | {numbers[index]})
        # 不包含當前元素
        backtrack(index + 1, curr_sum, curr_set)
    
    backtrack(0, 0, set())
    return result

numbers = [1, 2, 3, 4]
target = 7
print(subset_sum(numbers, target))  # {3, 4}