1. 集合概述

1.1 什麼是集合?

Python 的集合(set)是一個無序、可變、不允許重複元素的容器,用於存儲唯一的數據項。集合基於哈希表實現,提供 O(1) 平均時間複雜度的成員檢查和插入操作。Python 還提供不可變的集合變體——凍結集合(frozenset),適用於需要不可變鍵的場景(如字典鍵)。

關鍵特性

  • 無序性:集合中的元素沒有固定順序,無法通過索引訪問。
  • 唯一性:自動去除重複元素。
  • 可變性set 可修改,frozenset 不可修改。
  • 哈希表:高效的查找和操作性能。
  • 內置支持:無需導入模塊,直接使用。

示例

# 創建集合
s = {1, 2, 3, 3}  # 自動去重
print(s)  # {1, 2, 3}

# 創建凍結集合
fs = frozenset([1, 2, 2])
print(fs)  # frozenset({1, 2})

1.2 集合與其它數據類型的對比

數據類型

有序性

可變性

允許重複

索引訪問

典型用途

列表 (list)

有序

可變

動態序列存儲

元組 (tuple)

有序

不可變

不可變數據

字典 (dict)

無序(3.7+有序)

可變

鍵唯一

鍵訪問

鍵值映射

集合 (set)

無序

可變

去重、集合運算

凍結集合 (frozenset)

無序

不可變

不可變鍵

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}
print(s)  # {1, 2, 3}

使用 set() 構造函數

s = set([1, 2, 2, 3])  # 從列表創建,自動去重
print(s)  # {1, 2, 3}

創建空集合

empty_set = set()  # 正確:空集合
not_empty = {}  # 錯誤:創建空字典
print(type(empty_set))  # <class 'set'>
print(type(not_empty))  # <class 'dict'>

創建凍結集合

fs = frozenset([1, 2, 3])
print(fs)  # frozenset({1, 2, 3})

注意

  • 空集合必須用 set() 創建,{} 默認是字典。
  • 集合元素必須是可哈希的(不可變類型,如 intstrtuple)。

2.2 基本操作

添加元素

  • add():添加單個元素。
s = {1, 2}
s.add(3)
print(s)  # {1, 2, 3}

移除元素

  • remove():移除元素,不存在則拋出 KeyError
  • discard():移除元素,不存在不報錯。
  • pop():隨機移除並返回一個元素。
  • clear():清空集合。
s = {1, 2, 3}
s.remove(2)  # {1, 3}
s.discard(4)  # 無錯誤
print(s.pop())  # 隨機移除,例如 1
s.clear()  # set()

成員檢查

  • 使用 in 和 not in 檢查元素是否存在。
s = {1, 2, 3}
print(2 in s)  # True
print(4 not in s)  # True

長度與迭代

  • len():獲取元素數量。
  • 迭代:使用 for 循環。
s = {1, 2, 3}
print(len(s))  # 3
for item in s:
    print(item)  # 1, 2, 3(順序不定)

2.3 集合運算

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

  • 並集(Union)| 或 union()
  • 交集(Intersection)& 或 intersection()
  • 差集(Difference)- 或 difference()
  • 對稱差集(Symmetric Difference)^ 或 symmetric_difference()
  • 子集/超集issubset()issuperset()

示例

a = {1, 2, 3}
b = {2, 3, 4}

# 並集
print(a | b)  # {1, 2, 3, 4}
print(a.union(b))  # {1, 2, 3, 4}

# 交集
print(a & b)  # {2, 3}
print(a.intersection(b))  # {2, 3}

# 差集
print(a - b)  # {1}
print(a.difference(b))  # {1}

# 對稱差集
print(a ^ b)  # {1, 4}
print(a.symmetric_difference(b))  # {1, 4}

# 子集/超集
print({1, 2}.issubset(a))  # True
print(a.issuperset({1, 2}))  # True

注意

  • 運算符(如 |)比方法(如 union())更簡潔,但方法支持更多參數(如多個集合)。
  • 凍結集合支持所有集合運算,但不可修改。

3. 高級用法與技巧

3.1 集合推導式

類似列表推導式,集合推導式可以快速創建集合:

# 創建平方集合
squares = {x**2 for x in range(5)}
print(squares)  # {0, 1, 4, 9, 16}

# 過濾偶數
evens = {x for x in range(10) if x % 2 == 0}
print(evens)  # {0, 2, 4, 6, 8}

技巧

  • 結合條件過濾複雜數據:
words = ["apple", "banana", "app"]
short_words = {w for w in words if len(w) < 5}
print(short_words)  # {'app'}

3.2 凍結集合的應用

frozenset 是不可變的集合,可作為字典鍵或集合元素:

# 用作字典鍵
d = {frozenset([1, 2]): "pair"}
print(d[frozenset([1, 2])])  # pair

# 嵌套集合
nested = {frozenset([1, 2]), frozenset([3, 4])}
print(nested)  # {frozenset({1, 2}), frozenset({3, 4})}

技巧

  • 使用 frozenset 存儲不可變配置:
valid_codes = frozenset(["A001", "B002"])

3.3 集合的性能優化

集合基於哈希表,查找和插入操作的時間複雜度為 O(1),遠優於列表的 O(n):

import timeit

# 列表查找
lst = list(range(10000))
print(timeit.timeit("9999 in lst", setup="lst = list(range(10000))", number=1000))
# 輸出:約 0.5 秒

# 集合查找
s = set(range(10000))
print(timeit.timeit("9999 in s", setup="s = set(range(10000))", number=1000))
# 輸出:約 0.01 秒

技巧

  • 去重:從列表去重時,優先使用集合。
lst = [1, 2, 2, 3]
unique = list(set(lst))  # [1, 2, 3]
  • 批量操作:使用 update() 添加多個元素。
s = {1, 2}
s.update([3, 4, 5])
print(s)  # {1, 2, 3, 4, 5}

3.4 類型註解

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

from typing import Set
def unique_names(names: list[str]) -> Set[str]:
    return set(names)

names = ["Alice", "Bob", "Alice"]
print(unique_names(names))  # {'Alice', 'Bob'}

技巧

  • 使用 FrozenSet 註解不可變集合:
from typing import FrozenSet
def get_codes() -> FrozenSet[str]:
    return frozenset(["A001", "A002"])

3.5 集合的動態轉換

集合可與其它數據類型互轉:

  • 從列表/元組
lst = [1, 2, 2, 3]
s = set(lst)  # {1, 2, 3}
t = tuple(lst)
s2 = set(t)  # {1, 2, 3}
  • 轉為列表/元組
s = {1, 2, 3}
lst = list(s)  # [1, 2, 3](順序不定)
t = tuple(s)  # (1, 2, 3)

技巧

  • 使用 sorted() 按順序轉換:
s = {3, 1, 2}
sorted_list = sorted(s)  # [1, 2, 3]

3.6 集合運算的高級用法

  • 多集合操作
sets = [{1, 2}, {2, 3}, {3, 4}]
union = set().union(*sets)
print(union)  # {1, 2, 3, 4}
  • 鏈式運算
a = {1, 2}
b = {2, 3}
c = {3, 4}
result = a | b & c
print(result)  # {3}

技巧

  • 使用 reduce 處理多集合運算:
from functools import reduce
result = reduce(lambda x, y: x | y, sets)
print(result)  # {1, 2, 3, 4}

4. 集合的實際應用場景

4.1 數據去重

場景:從用户輸入中提取唯一值。

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

技巧

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

4.2 快速成員檢查

場景:驗證用户權限。

allowed_users = {"Alice", "Bob"}
if "Charlie" in allowed_users:
    print("Access granted")
else:
    print("Access denied")  # 輸出:Access denied

技巧

  • 使用集合替代列表,加速查找:
# 列表:慢
users = ["Alice"] * 10000
print("Alice" in users)  # O(n)
# 集合:快
user_set = set(users)
print("Alice" in user_set)  # O(1)

4.3 數據分析

場景:比較兩個數據集的共同元素。

import pandas as pd
# 假設從 CSV 加載兩組客户 ID
df1 = pd.DataFrame({"id": [1, 2, 3]})
df2 = pd.DataFrame({"id": [2, 3, 4]})
common_ids = set(df1["id"]) & set(df2["id"])
print(common_ids)  # {2, 3}

技巧

  • 使用集合處理大數據集的交集、差集:
unique_ids = set(df1["id"]) - set(df2["id"])
print(unique_ids)  # {1}

4.4 算法優化

場景:實現兩數之和(LeetCode #1)。

def two_sum(nums: list[int], target: int) -> list[int]:
    """Find indices of two numbers that sum to target.

    Args:
        nums (list[int]): List of numbers.
        target (int): Target sum.

    Returns:
        list[int]: Indices of the two numbers.
    """
    seen = set()
    for i, num in enumerate(nums):
        complement = target - num
        if complement in seen:
            return [nums.index(complement), i]
        seen.add(num)
    return []

技巧

  • 使用集合的 O(1) 查找優化算法。
  • 結合字典存儲索引以返回結果。

4.5 遊戲開發(參考 TrafficFlowGame)

場景:管理遊戲中的唯一對象(如障礙物)。

# 障礙物座標
obstacles = {(10, 20), (20, 30), (10, 20)}
print(obstacles)  # {(10, 20), (20, 30)} 自動去重

技巧

  • 使用 frozenset 存儲不可變遊戲狀態:
game_state = frozenset(obstacles)
states = {game_state: "level1"}

5. 性能分析與優化

5.1 集合 vs 列表

集合的哈希表實現使其在以下操作中更高效:

  • 成員檢查in 操作,集合 O(1),列表 O(n)。
  • 去重:集合自動去重,效率高於列表循環。
  • 集合運算:交集、並集等操作優化為哈希運算。

示例(性能對比):

import timeit
lst = list(range(10000))
s = set(lst)

# 查找
print(timeit.timeit("9999 in lst", setup="lst = list(range(10000))", number=1000))
# 輸出:約 0.5 秒
print(timeit.timeit("9999 in s", setup="s = set(range(10000))", number=1000))
# 輸出:約 0.01 秒

# 去重
lst_with_dups = lst + lst
print(timeit.timeit("list(dict.fromkeys(lst_with_dups))", setup="lst_with_dups = list(range(10000)) * 2", number=1000))
# 輸出:約 0.8 秒
print(timeit.timeit("set(lst_with_dups)", setup="lst_with_dups = list(range(10000)) * 2", number=1000))
# 輸出:約 0.2 秒

5.2 內存優化

集合的內存佔用通常低於列表,因為無需存儲重複元素:

import sys
lst = [1, 2, 3] * 1000
s = set(lst)
print(sys.getsizeof(lst))  # 約 36000 字節
print(sys.getsizeof(s))   # 約 9000 字節

技巧

  • 對於大數據量,優先使用集合存儲唯一值。
  • 使用 frozenset 節省內存(不可變對象更緊湊)。

5.3 批量操作

  • 高效添加:使用 update() 而非多次 add()
s = set()
s.update(range(1000))  # 更快
# 慢:for i in range(1000): s.add(i)
  • 批量移除:使用 difference_update()
s = {1, 2, 3, 4}
s.difference_update({2, 3})
print(s)  # {1, 4}

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

6.1 代碼檢查工具

  • flake8:檢查集合相關代碼規範。
pip install flake8
flake8 myfile.py
  • pylint:確保集合使用正確。
pip install pylint
pylint myfile.py
  • Mypy:驗證類型註解。
from typing import Set
def process(s: Set[int]) -> Set[int]:
    return s

6.2 IDE 支持

  • VS Code
  • 插件:Python、Pylance(類型檢查)。
  • 快捷鍵:Ctrl+/ 註釋代碼。
  • PyCharm
  • 自動提示集合方法(如 addunion)。
  • 內置類型檢查。
  • Jupyter Notebook
  • 交互式測試集合操作。
s = {1, 2, 3}
s | {4, 5}  # 交互顯示 {1, 2, 3, 4, 5}

6.3 文檔生成

  • Sphinx:生成集合相關函數的文檔。
def unique_items(items: list) -> set:
    """Remove duplicates from a list.

    :param items: List of items.
    :returns: Set of unique items.
    """
    return set(items)
  • 運行 make html 生成文檔。
  • pydoc:快速查看集合方法。
python -m pydoc set

7. 常見問題與解決方案

7.1 集合無序性

  • 問題:集合無法索引或保持順序。
  • 解決:轉換為列表並排序。
s = {3, 1, 2}
sorted_list = sorted(s)  # [1, 2, 3]

7.2 不可哈希元素

  • 問題:嘗試添加列表等不可哈希對象。
  • 解決:使用元組或凍結集合。
# 錯誤
s = {1, [2, 3]}  # TypeError: unhashable type: 'list'
# 正確
s = {1, tuple([2, 3])}  # {1, (2, 3)}

7.3 KeyError in remove()

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

7.4 凍結集合不可修改

  • 問題:嘗試修改 frozenset
  • 解決:轉換為 set 修改。
fs = frozenset([1, 2])
s = set(fs)
s.add(3)

7.5 集合運算性能

  • 問題:對大數據集執行集合運算效率低。
  • 解決
  • 確保輸入數據已去重。
  • 分批處理超大數據集:
def batch_union(sets: list[set]) -> set:
    result = set()
    for s in sets:
        result.update(s)
    return result

8. 項目實踐:集合在實際開發中的應用

8.1 數據去重與清洗

場景:處理用户上傳的重複數據。

def clean_data(emails: list[str]) -> set[str]:
    """Remove duplicate email addresses.

    Args:
        emails (List[str]): List of email addresses.

    Returns:
        Set[str]: Unique email addresses.
    """
    return set(email.lower() for email in emails)

emails = ["a@example.com", "A@example.com", "b@example.com"]
print(clean_data(emails))  # {'a@example.com', 'b@example.com'}

8.2 權限管理

場景:檢查用户權限交集。

def check_access(user_roles: set[str], required_roles: set[str]) -> bool:
    """Check if user has required roles.

    Args:
        user_roles (Set[str]): User's roles.
        required_roles (Set[str]): Required roles.

    Returns:
        bool: True if user has all required roles.
    """
    return required_roles.issubset(user_roles)

user = {"admin", "editor"}
required = {"editor"}
print(check_access(user, required))  # True

8.3 數據分析

場景:比較兩組客户數據。

import pandas as pd
def find_new_customers(old_ids: set[int], new_ids: set[int]) -> set[int]:
    """Find new customers added in new dataset.

    Args:
        old_ids (Set[int]): Previous customer IDs.
        new_ids (Set[int]): Current customer IDs.

    Returns:
        Set[int]: IDs of new customers.
    """
    return new_ids - old_ids

df_old = pd.DataFrame({"id": [1, 2, 3]})
df_new = pd.DataFrame({"id": [2, 3, 4]})
new_customers = find_new_customers(set(df_old["id"]), set(df_new["id"]))
print(new_customers)  # {4}

8.4 遊戲開發(參考 TrafficFlowGame)

場景:管理遊戲中的唯一障礙物。

def update_obstacles(current: set[tuple], new: list[tuple]) -> set[tuple]:
    """Update obstacle positions, removing duplicates.

    Args:
        current (Set[tuple]): Current obstacle coordinates.
        new (List[tuple]): New obstacle coordinates.

    Returns:
        Set[tuple]: Updated unique obstacles.
    """
    return current | set(new)

obstacles = {(10, 20), (20, 30)}
new_obstacles = [(20, 30), (30, 40)]
updated = update_obstacles(obstacles, new_obstacles)
print(updated)  # {(10, 20), (20, 30), (30, 40)}

8.5 算法優化

場景:實現子集和問題。

def subset_sum(nums: list[int], target: int) -> bool:
    """Check if a subset sums to target.

    Args:
        nums (List[int]): List of numbers.
        target (int): Target sum.

    Returns:
        bool: True if subset exists.
    """
    seen = {0}
    for num in nums:
        new_sums = {s + num for s in seen}
        seen.update(new_sums)
        if target in seen:
            return True
    return False

print(subset_sum([3, 5, 8], 8))  # True