还在乱用列表?集合 set 才是去重和效率神器
不是会不会写代码,而是会不会选结构
你是否曾为列表去重写下一段循环判断,却在数据量激增时遭遇性能瓶颈?或者,一个简单的成员检查就让程序响应迟缓?
这通常不是算法逻辑的缺陷,而是数据结构选择失误。许多开发者习惯性地将列表(list)作为万能容器,却忽略了集合(set)在特定场景下带来的性能飞跃。
你是否写过类似的代码?
users = [1001, 1002, 1002, 1003, 1003, 1003]
result = []
for u in users:
if u not in result:
result.append(u)
代码看似逻辑清晰,但面临海量数据时,其性能衰减会非常显著,代码也显得冗长。
关键在于工具的选择。在工程实践中,正确的数据结构能将性能提升一个数量级。
为什么list会越来越慢?
以最常见的成员检查为例:
nums = list(range(1000000))
999999 in nums
这行代码在底层执行线性搜索,即从索引0开始逐个比对,直到找到目标或遍历结束。其时间复杂度为O(n),意味着耗时随数据规模线性增长。
这就像在一列长队中逐人辨认,效率低下。
set 为什么是效率神器?
同样的操作,使用集合:
nums = set(range(1000000))
999999 in nums
响应几乎是瞬时的。核心差异在于集合基于哈希表实现。
如果说列表是“排队找人”,集合则是“按身份证号精准定位”。每个元素通过哈希函数计算出一个唯一地址,查找时直接访问该地址,无需遍历。其时间复杂度为O(1),查找速度与数据量无关。
去重的3种写法(90%的人只会第一种)
理解原理后,我们对比三种去重方法的效率差异。
❌ 写法1:低效循环版
result = []
for n in nums:
if n not in result:
result.append(n)
这是最直观但效率最低的方法。每次`in`检查都需遍历结果列表,整体时间复杂度为O(n²),数据量大时性能急剧下降。
✅ 写法2:set去重(最常用)
result = list(set(nums))
利用集合自动去重的特性,一行代码高效完成任务。但需注意:原始顺序会丢失,因为集合是无序的。
? 写法3:保序去重(推荐)
result = list(dict.fromkeys(nums))
若需保留元素首次出现的顺序,推荐此方法。从Python 3.7起,字典保持键的插入顺序。`dict.fromkeys(nums)`以列表元素为键创建字典,自动去重并保序,最后转换回列表。
真实项目场景
以下实战场景展示了集合的应用价值。
场景1:百万级用户ID去重
user_ids = load_data() # 假设这里加载了百万条数据
unique_users = set(user_ids)
列表循环可能需要数秒,而集合操作通常在毫秒级完成,适用于日志分析或用户数据处理。
场景2:黑名单快速过滤
blacklist = {1002, 1005, 1010}
users = [1001, 1002, 1003]
safe_users = [u for u in users if u not in blacklist]
将黑名单定义为集合,使成员检查变为O(1)操作,即使黑名单规模庞大,过滤速度依然极快。
场景3:寻找共同好友(集合运算)
a = {1, 2, 3} # 用户A的好友集合
b = {2, 3, 4} # 用户B的好友集合
print(a & b) # 输出: {2, 3},即共同好友
集合支持交集(&)、并集(|)、差集(-)等运算,在社交网络或推荐系统中处理群体关系时极为高效。
set 的隐藏技巧
除了核心功能,集合还有一些实用技巧:
自动去重并清理空值:
data = [1, 2, None, 2, None]
clean = {x for x in data if x is not None}
# 结果: {1, 2}
使用集合推导式,可同步完成过滤与去重。
快速提取字符串中的唯一字符:
text = "aaabbbccc"
print(set(text)) # 输出: {'a', 'b', 'c'}
一行代码判断列表是否有重复元素:
def has_duplicate(nums):
return len(nums) != len(set(nums))
# 如果列表长度和去重后的集合长度不等,说明一定有重复。
set 的三个大坑
集合并非万能,使用时需注意以下限制:
❌ 无序性:集合不保证元素的存储顺序。
print(set([3, 1, 2])) # 输出可能是 {1, 2, 3},顺序不确定。
❌ 不能存放可变对象:集合元素必须是可哈希的。列表、字典等可变对象不可哈希,不能作为集合元素。
s = {[1, 2]} # 报错!TypeError: unhashable type: 'list'
❌ 非线程安全(进阶注意):在多线程环境下并发修改同一集合可能导致问题,在并发编程中需谨慎处理。
list vs set 认知升级
编程能力的瓶颈,往往不在于语法掌握,而在于数据结构的合理运用。
高手的思维包含两层:如何实现逻辑,以及如何用最优的数据结构组织数据。后者直接决定了程序的执行效率与可维护性。
核心原则:凡是涉及成员查找(`in`操作)或去重的场景,应优先考虑使用集合(set)而非列表(list)。
最后给你一个练习
为巩固理解,尝试实现一个函数,输入一个列表,要求返回:
- 去重后的结果(保留原始顺序)。
- 一个布尔值,指示原列表是否存在重复元素。
- 一个列表,包含所有重复出现的元素(每个重复元素只列一次)。
你会如何设计这个函数?
