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)。 - 添加/移除(
add,remove,discard):平均 O(1)。 - 集合運算(
union,intersection等):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}