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 集合與其它數據類型的對比
|
數據類型 |
有序性 |
可變性 |
允許重複 |
索引訪問 |
典型用途 |
|
列表 ( |
有序 |
可變 |
是 |
是 |
動態序列存儲 |
|
元組 ( |
有序 |
不可變 |
是 |
是 |
不可變數據 |
|
字典 ( |
無序(3.7+有序) |
可變 |
鍵唯一 |
鍵訪問 |
鍵值映射 |
|
集合 ( |
無序 |
可變 |
否 |
否 |
去重、集合運算 |
|
凍結集合 ( |
無序 |
不可變 |
否 |
否 |
不可變鍵 |
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()創建,{}默認是字典。 - 集合元素必須是可哈希的(不可變類型,如
int、str、tuple)。
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:
- 自動提示集合方法(如
add,union)。 - 內置類型檢查。
- 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