Python 集合交、并、差和对称差不是只停留在数学概念,活跃用户分析、渠道邮箱合并、权限过滤、增量更新等场景都能直接落地。集合基于哈希表,成员查找和增删平均为 O(1),因此这些运算通常比列表循环更快。下面按运算语义、代码写法、方法和运算符差异、性能与陷阱重组原文内容。
【四种运算与写法】
并集:a | b 或 a.union(b),返回所有元素并去重;原地写法 a |= b,等价 a.update(b)。
交集:a & b 或 a.intersection(b),返回共有元素;原地 a &= b,等价 a.intersection_update(b)。
差集:a - b 或 a.difference(b),返回在 a 不在 b 的元素;差集不对称,a-b 与 b-a 结果不同;原地 a -= b,等价 a.difference_update(b)。
对称差:a ^ b 或 a.symmetric_difference(b),返回只在其中一个集合出现的元素;数学上 a ^ b 等于 (a-b)|(b-a),也等于 (a|b)-(a&b);原地 a ^= b,等价 a.symmetric_difference_update(b)。
方法通常可接受列表、元组、range、字符串、字典键等任意可迭代对象;运算符要求两边都是集合。union、intersection、difference 可一次传多个参数,symmetric_difference 只接受一个参数,多集合对称差可用 ^ 链式,但结果与计算顺序有关。
【活跃用户示例】- yesterday = {'alice', 'bob', 'charlie', 'david'}
- today = {'alice', 'charlie', 'eve', 'frank'}
- loyal = yesterday & today
- churned = yesterday - today
- new_users = today - yesterday
- all_users = yesterday | today
- print(f'忠实用户: {loyal}')
- print(f'流失用户: {churned}')
- print(f'新用户: {new_users}')
- print(f'全部用户: {all_users}')
复制代码 差集要区分方向:yesterday-today 是流失,today-yesterday 是新增。交集求连续活跃,并集求所有活跃过的用户。
【合并多数据源】- email_sources = {
- 'web_registration': {'alice@test.com', 'bob@test.com', 'charlie@test.com'},
- 'mobile_app': {'bob@test.com', 'david@test.com', 'eve@test.com'},
- 'api_import': {'charlie@test.com', 'frank@test.com', 'grace@test.com'},
- 'manual_entry': {'alice@test.com', 'henry@test.com'},
- }
- all_emails = set().union(*email_sources.values())
- print(f'总邮箱数: {len(all_emails)}')
- for source, emails in email_sources.items():
- unique_to_source = len(emails - all_emails)
- print(f'{source}: {len(emails)}个邮箱, 其中独有{unique_to_source}个')
- all_emails2 = set()
- for emails in email_sources.values():
- all_emails2 |= emails
- print(f'用|=合并: {len(all_emails2)}个邮箱')
复制代码 这里有一个原文代码逻辑点值得注意:emails - all_emails 按字面会得到空集,因为 all_emails 已经包含全部邮箱;如果要统计“该渠道独有”,应改为用 emails 减去其他渠道的并集。
【交集筛选与权限过滤】- purchased_last_month = {'alice', 'bob', 'charlie', 'david', 'eve'}
- subscribed_newsletter = {'alice', 'charlie', 'frank', 'grace'}
- left_positive_review = {'alice', 'david', 'frank', 'henry'}
- premium_member = {'alice', 'bob', 'charlie', 'grace', 'henry'}
- most_loyal = (
- purchased_last_month
- & subscribed_newsletter
- & left_positive_review
- & premium_member
- )
- print(f'四项全满足的用户: {most_loyal}')
- quality_buyers = purchased_last_month & left_positive_review
- print(f'购买并好评的用户: {quality_buyers}')
- subscribed_no_review = subscribed_newsletter - left_positive_review
- print(f'订阅但未好评: {subscribed_no_review}')
复制代码 权限过滤也是同样思路:先定义角色权限集,再用差集排除禁用操作,用交集检查某角色是否包含被限制的权限。- role_editor = {'read', 'write', 'export'}
- forbidden_for_editor = {'delete', 'admin', 'import'}
- editor_actual = role_editor - forbidden_for_editor
- print(f'Editor实际权限: {editor_actual}')
- role_admin = {'read', 'write', 'delete', 'admin', 'export', 'import'}
- admin_actual = role_admin & forbidden_for_editor
- print(f'Admin被限制的操作: {admin_actual}')
复制代码
【增量更新与版本差异】
差集适合找出新增和删除,交集适合找出保持不变的部分。- old_data = {'user_001', 'user_002', 'user_003', 'user_004', 'user_005'}
- new_data = {'user_002', 'user_003', 'user_005', 'user_006', 'user_007'}
- to_add = new_data - old_data
- to_delete = old_data - new_data
- to_keep = old_data & new_data
- print(f'需要新增: {to_add}')
- print(f'需要删除: {to_delete}')
- print(f'保持不变: {to_keep}')
复制代码 对称差用来找两个版本之间所有变化的功能,再配合单向差集区分新增和删除。- version_1_features = {'login', 'register', 'profile', 'search', 'cart'}
- version_2_features = {'login', 'register', 'profile', 'search', 'cart', 'chat', 'analytics'}
- changes = version_1_features ^ version_2_features
- added = version_2_features - version_1_features
- removed = version_1_features - version_2_features
- print(f'变更的功能: {changes}')
- print(f'新增: {added}')
- print(f'删除: {removed}')
复制代码 标签差异场景中,交集是共同标签,对称差是不同标签,两个单向差集分别是各自独有标签;原文还验证了对称差等于两边独有标签的并集。
【性能特性与运算符选择】
原文给出的时间复杂度为:add、remove、x in s 平均 O(1);a | b 为 O(len(a)+len(b));a & b 为 O(min(len(a),len(b)));a - b 为 O(len(a));a ^ b 为 O(len(a)+len(b))。因为基于哈希表,集合运算通常比等价的列表循环快 100 到 1000 倍。原文用 time.perf_counter 对比了集合并集、交集与列表模拟写法,列表方案需要减少循环次数才能完成测试。
选择上:两边都是集合、需要链式写法时优先用运算符;操作数是列表、元组等非集合类型,或需要一次传多个参数时用方法。原地操作会修改调用方集合,例如 a |= b;而 a = a | b 会创建新对象。除非有明确性能需求,优先创建新对象更安全,避免意外修改原数据。
【常见陷阱】
原文提醒四类问题:运算符两边类型必须一致,集合不能直接和列表做 |、&、-、^;混合表达式要注意运算符优先级,必要时加括号;空集合要单独处理,可用 set() 作为起点,例如 set().union([1,2,3], [3,4,5], range(5,8));集合元素必须可哈希,可变元素会导致运算失败。差集方向、symmetric_difference 不接受多个参数、多集合对称差依赖计算顺序,也都是实际编码时需要留意的细节。
总体来看,集合运算适合做用户分组、权限校验、数据增量同步、标签对比和版本变更分析。把运算符和方法选对,再注意原地修改与差集方向,代码通常比列表循环更短,也更符合集合语义。 |