Python 的集合(set)不仅支持去重,还内置了四种基本集合运算:并集、交集、差集和对称差集。这些运算对应数学中的集合论,但在代码中只需要一个运算符或方法即可完成,且性能极高(基于哈希表实现)。本文会用实际案例逐一讲解每种运算的运算符、方法、原地操作以及适用场景,最后给出一个好友推荐系统的实现示例。
一、并集(Union):合并所有元素并去重
并集返回两个(或多个)集合中的所有元素,自动去重。Python 提供两种方式:
- 运算符 `|`:两边都必须是集合。
- 方法 `union()`:可以接受任意可迭代对象(列表、元组、range、字典的键等)。
示例:- a = {1, 2, 3, 4}
- b = {3, 4, 5, 6}
- print(a | b) # {1, 2, 3, 4, 5, 6}
- print(a.union([4, 5])) # {1, 2, 3, 4, 5},参数是列表
复制代码
当需要合并多个集合时,可以用链式 `|` 或 `union()` 传入多个参数:- a = {1, 2}
- b = {2, 3}
- c = {3, 4}
- result = a | b | c # {1, 2, 3, 4}
- result = a.union(b, c) # 同上
复制代码
原地操作 `|=` 会直接修改原集合:- a = {1, 2, 3}
- b = {3, 4, 5}
- a |= b # a 变为 {1, 2, 3, 4, 5},原对象被修改
复制代码
实战:合并多个数据源的用户邮箱。- email_sources = {
- 'web': {'alice@test.com', 'bob@test.com'},
- 'mobile': {'bob@test.com', 'david@test.com'},
- 'api': {'charlie@test.com', 'frank@test.com'},
- }
- all_emails = set().union(*email_sources.values())
- print(all_emails) # {'alice', 'bob', 'charlie', 'david', 'frank'}
复制代码
二、交集(Intersection):找出共有元素
交集返回同时存在于所有集合中的元素。运算符 `&` 要求两边都是集合,方法 `intersection()` 可接受可迭代对象。- a = {1, 2, 3, 4}
- b = {3, 4, 5, 6}
- print(a & b) # {3, 4}
- print(a.intersection([3, 4, 5])) # {3, 4}
复制代码
多个集合求交集:- a = {1, 2, 3, 4, 5}
- b = {2, 3, 4, 5, 6}
- c = {3, 4, 5, 6, 7}
- print(a & b & c) # {3, 4, 5}
- print(a.intersection(b, c)) # 同上
复制代码
原地操作 `&=` 会修改原集合。
实战:找同时满足多个条件的用户。- purchased = {'alice', 'bob', 'charlie'}
- subscribed = {'alice', 'charlie', 'david'}
- reviewed = {'alice', 'david', 'frank'}
- loyal = purchased & subscribed & reviewed
- print(loyal) # {'alice'}
复制代码
三、差集(Difference):获取独有的元素
差集返回前一个集合中去除与后一个集合共有元素后的剩余元素,**不对称**。运算符 `-` 要求两边都是集合,方法 `difference()` 可接受可迭代对象。- a = {1, 2, 3, 4, 5}
- b = {4, 5, 6, 7, 8}
- print(a - b) # {1, 2, 3}
- print(b - a) # {6, 7, 8}
- print(a.difference([4, 5])) # {1, 2, 3}
复制代码
多个集合求差集时,按顺序连续减去:- a = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
- b = {1, 2, 3}
- c = {4, 5}
- result = a - b - c # 或 a.difference(b, c)
- print(result) # {6, 7, 8, 9, 10}
复制代码
原地操作 `-=`。
实战:权限管理——排除被禁用的操作。- all_perms = {'read', 'write', 'delete', 'admin', 'export', 'import'}
- role_editor = {'read', 'write', 'export'}
- forbidden = {'delete', 'admin', 'import'}
- actual = role_editor - forbidden
- print(actual) # {'read', 'write', 'export'}
复制代码
四、对称差集(Symmetric Difference):找出仅属于一个集合的元素
对称差集返回只出现在其中一个集合中的元素,排除共有部分。运算符 `^` 要求两边都是集合,方法 `symmetric_difference()` 只接受一个参数(不能多个)。- a = {1, 2, 3, 4}
- b = {3, 4, 5, 6}
- print(a ^ b) # {1, 2, 5, 6}
- print(a.symmetric_difference(b)) # 同上
复制代码
验证对称等价式:`a ^ b == (a - b) | (b - a) == (a | b) - (a & b)`。
原地操作 `^=`。
实战:比较两个版本的功能变更。- v1 = {'login', 'register', 'profile', 'search', 'cart'}
- v2 = {'login', 'register', 'profile', 'search', 'cart', 'chat', 'analytics'}
- changes = v1 ^ v2
- print(changes) # {'chat', 'analytics'}
- added = v2 - v1
- removed = v1 - v2
复制代码
五、运算符 vs 方法:如何选择
- 运算符(| & - ^):代码简洁,但操作数都必须是集合。
- 方法(union, intersection, difference, symmetric_difference):可以接受列表、元组等可迭代对象,而且 `union` 和 `intersection` 支持多参数。
- 原地运算符(|= &= -= ^=)修改原集合,建议仅在需要性能优化(如大数据量循环)时使用,通常应优先创建新对象以避免副作用。
六、性能对比:集合 vs 列表
集合运算基于哈希表,时间复杂度远低于列表的线性搜索。例如,对 10000 个元素求交集,集合比列表快 100 倍以上。实际测试中,集合运算瞬间完成,而列表推导式则需要数百倍时间。因此,在需要频繁判断成员关系的场景中,应优先使用集合。
七、综合实战:好友推荐系统
假设用户数据如下,推荐规则:如果 A 和 B 不是好友,但有共同好友,则推荐 B 给 A。- class FriendRecommender:
- def __init__(self, friendships):
- # friendships: dict of username -> set of friends
- self.friends = friendships
- def recommend(self, user):
- if user not in self.friends:
- return set()
- user_friends = self.friends[user]
- candidates = set()
- for friend in user_friends:
- # 每个好友的好友(排除自己和已好友)
- candidates |= self.friends[friend] - user_friends - {user}
- # 按共同好友数排序
- from collections import Counter
- common_counts = Counter()
- for candidate in candidates:
- common = user_friends & self.friends[candidate]
- common_counts[candidate] = len(common)
- # 返回按共同好友数降序排列的推荐列表
- return common_counts.most_common()
- # 示例数据
- friendships = {
- 'Alice': {'Bob', 'Charlie', 'David'},
- 'Bob': {'Alice', 'Charlie', 'Eve'},
- 'Charlie': {'Alice', 'Bob', 'Frank'},
- 'David': {'Alice'},
- 'Eve': {'Bob'},
- 'Frank': {'Charlie'},
- }
- recommender = FriendRecommender(friendships)
- print(recommender.recommend('Alice'))
复制代码
此实现利用并集、差集、交集高效找出候选人和计算共同好友数。
八、注意事项
- 运算符两边必须都是集合,否则会抛出 TypeError;方法则可以接受任何可迭代对象。
- 运算符优先级:`-` 优先级高于 `|` 和 `&`,但低于比较运算符,必要时加括号。
- 对称差集方法只能传一个参数;多个集合的对称差需用链式 `^`。
- 集合元素必须是不可变类型(如数字、字符串、元组),不能包含列表或集合,否则无法参与运算。
掌握集合的四类运算,可以大幅简化数据处理代码,尤其适用于去重、筛选、差异对比等场景。建议优先使用方法以避免类型转换,在性能敏感处使用原地操作。 |