Python列表去重的20种实现方式:从原理到工程实践
列表去重是Python开发中的高频需求,但不同实现方式在时间复杂度、空间占用及顺序保持上差异巨大。本文系统梳理了20种去重方案,从基础的O(n²)循环到高效的O(n)哈希方法,并结合实际工程场景(如保序、不可哈希元素、业务逻辑干预)给出选型建议,帮助开发者在AI编程时代理解底层原理,做出最优代码决策。
核心结论:工程首选方案
在大多数实际项目中,去重操作应优先考虑时间复杂度为O(n)且保持原有顺序的方案。对于Python 3.7及以上版本,最简洁且高效的方式是利用字典的键唯一性及插入顺序特性:
# 保序、O(n)、对所有可哈希类型有效,Python 3.7+ 自带
result = list(dict.fromkeys(data))
若不关心元素顺序,且追求代码极简,可直接使用集合转换:
result = list(set(data))
对于大规模数值数据,借助NumPy底层C实现的排序去重往往性能更佳:
import numpy as np
result = list(np.unique(data))
第一类:基础循环(方法1-8)——理解算法原理
这类方法通过显式遍历和比较来实现去重,时间复杂度通常为O(n²)。它们主要用于教学演示或理解“查找”与“添加”的基本逻辑,不建议在生产环境中使用。
方法1-2:线性查找与下标遍历
最直观的思路是新建一个列表,遍历原列表,若元素不在新列表中则追加。由于列表的`in`操作是O(n)扫描,整体复杂度为O(n²)。
# 方法1:最基础的线性查找
def unique_v1(data):
new_list = []
for item in data:
# in 在列表上是 O(n) 扫描,整体 O(n²)
if item not in new_list:
new_list.append(item)
return new_list
# 方法2:用下标遍历(逻辑同方法1,仅遍历方式不同)
def unique_v2(data):
new_list = []
for i in range(len(data)):
if data[i] not in new_list:
new_list.append(data[i])
return new_list
方法3-4:列表推导式与索引判断
利用列表推导式的副作用或`index()`方法判断元素是否首次出现。注意`data.index(x)`返回的是第一次出现的下标,若当前下标等于该值,说明是首次出现。
# 方法3:列表推导式(利用副作用追加)
def unique_v3(data):
new_list = []
[new_list.append(i) for i in data if i not in new_list]
return new_list
# 方法4:通过元素首次出现位置判断
def unique_v4(data):
new_list = []
for i in range(len(data)):
# data.index(x) 返回 x 在 data 里第一次出现的下标
if i == data.index(data[i]):
new_list.append(data[i])
return new_list
方法5-6:原地删除(从右/左扫描)
通过双重循环比较,发现重复元素后直接删除。虽然空间复杂度为O(1),但频繁删除列表元素会导致性能下降,且修改了原列表。
# 方法5:原地删除(从右往左扫描)
def unique_v5(data):
l = len(data)
while l > 0:
l -= 1
i = l
while i > 0:
i -= 1
# 在 [0, l) 区间里寻找与 data[l] 相同的元素
if data[i] == data[l]:
del data[l]
break
return data
# 方法6:原地删除(从左往右扫)
def unique_v6(data):
l = len(data)
i = 0
while i < l:
j = i + 1
while j < l:
if data[i] == data[j]:
del data[j]
l -= 1 # 列表变短,长度同步更新
else:
j += 1
i += 1
return data
方法7-8:异常处理与双层循环
方法7利用`index()`抛出`ValueError`异常来判断是否存在,这种方式在Python中通常被视为反模式,因为异常处理开销大且可读性差。方法8通过内层循环只比较到当前索引`i`,比较次数减半,但渐进复杂度仍为O(n²)。
# 方法7:用 try-except 替代 in(不推荐)
def unique_v7(data):
new_list = []
for item in data:
try:
new_list.index(item)
except ValueError:
new_list.append(item)
return new_list
# 方法8:双层循环+下标判断
def unique_v8(data):
new_list = []
for i in range(len(data)):
j = 0
while j <= i:
if data[i] == data[j]:
if i == j:
new_list.append(data[i])
break
j += 1
return new_list
第二类:哈希表(方法9-12)——工程首选方案
利用字典(dict)或集合(set)的键唯一性,将查询复杂度降至O(1),整体时间复杂度为O(n)。这是日常项目中的首选方案,但需要O(n)的额外内存空间,且要求元素必须是可哈希的(如数字、字符串、元组)。
方法9:Set + List 保序
结合集合的快速查找和列表的顺序保持,是工程中最常见的保序去重写法。
# 方法9:set 配合 list——工程最常见写法
def unique_v9(data):
seen = set() # set 用于 O(1) 判重
result = [] # list 用于保持原顺序
for item in data:
if item not in seen:
seen.add(item)
result.append(item)
return result
方法10:dict.fromkeys() —— 最佳版本
Python 3.7+ 中字典保持插入顺序,`fromkeys()`会自动用相同key覆盖,从而完成去重。这是最简洁且高效的一行代码实现。
# 方法10:dict.fromkeys()——最佳版本,实际使用首选
def unique_v10(data):
return list(dict.fromkeys(data))
方法11-12:Filter 函数式风格
利用`filter`配合闭包。方法11使用列表作为`seen`,导致复杂度退化为O(n²);方法12改用字典,查询变为O(1),但写法晦涩,不如直接循环清晰。
# 方法11:filter + 列表(复杂度O(n²),不推荐)
def unique_v11(data):
seen = []
def check(item):
if item not in seen:
seen.append(item)
return True
return False
return list(filter(check, data))
# 方法12:filter + 字典(复杂度O(n),但写法复杂)
def unique_v12(data):
obj = {}
def check(item):
if obj.get(item) is None:
obj[item] = item
return True
return False
return list(filter(check, data))
第三类:集合与排序(方法13-16)——不保序场景
这类方法通过集合转换或先排序再相邻比较实现去重。集合方式复杂度为O(n),排序方式复杂度为O(nlogn)。它们均不保留原有顺序,适用于统计去重数量或做集合运算的场景。
# 方法13:set 直接转列表(不保序)
def unique_v13(data):
return list(set(data))
# 方法14:map + filter 组合(不保序,复杂度O(n²))
def unique_v14(data):
seen = []
def mark(item):
if item not in seen:
seen.append(item)
return item
return None
return list(filter(lambda x: x is not None, map(mark, data)))
# 方法15-16:先排序再相邻去重(不保序,复杂度O(nlogn))
def unique_v15(data):
data.sort() # 排序后,相同元素会聚到一起
l = len(data)
while l > 0:
l -= 1
if l > 0 and data[l] == data[l-1]:
del data[l]
return data
def unique_v16(data):
data.sort()
l = len(data) - 1
i = 0
while i < l:
if data[i] == data[i+1]:
del data[i]
i -= 1
l -= 1
i += 1
return data
第四类:函数式与递归(方法17-20)——思维练习
这类方法主要展示函数式编程(reduce)和递归的思想,通常晦涩且存在栈溢出风险,仅用于学习或思维练习。
方法17:Reduce + 元组累加器
利用`functools.reduce`配合元组累加器(包含结果列表和集合),在保持O(n)复杂度的同时实现保序,但写法比直接循环晦涩。
# 方法17:reduce + 元组累加器(函数式风格但能跑到 O(n))
import functools
def unique_v17(data):
def foo(acc, item):
result, seen = acc
if item in seen:
return (result, seen)
seen.add(item)
result.append(item)
return (result, seen)
return functools.reduce(foo, data, ([], set()))[0]
方法18:Numpy Unique
利用NumPy底层C实现的排序去重,适合大规模数值数据,但不保序。
# 方法18:调用 numpy.unique
def unique_v18(data):
import numpy as np
return list(np.unique(np.array(data)))
方法19-20:递归实现
通过递归检查末尾元素是否重复并删除,或拼接结果。递归深度为n,大数据会导致栈溢出,且切片操作产生大量新列表,空间开销大。
# 方法19:递归+原地删除(栈溢出风险)
def unique_v19(data, length=None):
if length is None:
length = len(data)
if length <= 1:
return data
last_idx = length - 1
is_repeat = False
for i in range(last_idx):
if data[i] == data[last_idx]:
is_repeat = True
break
if is_repeat:
del data[last_idx]
return unique_v19(data, length - 1)
# 方法20:递归+拼接返回(空间开销大)
def unique_v20(data, length=None):
if length is None:
length = len(data)
if length <= 1:
return data
last_idx = length - 1
last_item = data[last_idx]
is_repeat = False
for i in range(last_idx):
if data[i] == last_item:
is_repeat = True
break
result = [] if is_repeat else [last_item]
return unique_v20(data[:last_idx], length - 1) + result
进阶:带逻辑干预与不可哈希元素处理
当去重需要结合业务逻辑(如保留最高分、累加计数、区间过滤)时,简单的`set`或`dict.fromkeys`无法直接表达。此时需将判重与规则处理分开。
带逻辑干预的去重
通过`key`指定去重键,`on_duplicate`回调处理重复项。例如,按ID去重并保留分数最高的学生记录。
def unique_with_rule(data, key=None, on_duplicate=None):
"""
带逻辑干预的去重。
key: 可哈希的去重键,默认拿元素本身
on_duplicate: 遇到重复时如何处理 (旧值, 新值) -> 新的"代表值"
返回 None 时保持旧值不变(即等同于丢弃新值)
"""
if key is None:
key = lambda x: x
chosen = {} # 键 -> 当前选中的元素
order = [] # 记录键首次出现的顺序,保证保序
for item in data:
k = key(item)
if k not in chosen:
chosen[k] = item
order.append(k)
elif on_duplicate is not None:
merged = on_duplicate(chosen[k], item)
if merged is not None:
chosen[k] = merged
return [chosen[k] for k in order]
# 示例:按id去重,保留分数最高的学生
students = [
{'id': 1, 'name': '张三', 'score': 90},
{'id': 1, 'name': '张三', 'score': 95}, # 同 id,分数更高
{'id': 2, 'name': '李四', 'score': 99},
]
result = unique_with_rule(
students,
key=lambda x: x['id'],
on_duplicate=lambda old, new: new if new['score'] > old['score'] else old,
)
处理不可哈希的元素
对于字典、列表等不可哈希元素,需自定义`key`函数将其映射为可哈希的键。此外,利用`set.add()`返回None的特性,可以写出一行代码的去重,但可读性极差,不建议在实际代码中使用。
# 推荐:自定义key函数处理不可哈希元素
def deduplicate_with_key(data, key=None):
if key is None:
return list(dict.fromkeys(data))
seen = set()
result = []
for item in data:
k = key(item)
if k not in seen:
seen.add(k)
result.append(item)
return result
# 不推荐:利用set.add()返回None的技巧(可读性差)
def deduplicate_short(data):
seen = set()
return [x for x in data if not (x in seen or seen.add(x))]
总结
Python列表去重的选型应基于顺序需求、数据规模和元素类型:
- 默认首选:使用`list(dict.fromkeys(data))`,保序且O(n)。
- 不保序:使用`list(set(data))`,代码最简。
- 大规模数值:使用`numpy.unique()`,性能最优。
- 不可哈希元素:使用自定义`key`函数映射为可哈希键。
- 业务逻辑去重:将判重(哈希结构)与规则处理(回调函数)分离。
在AI编程时代,理解这些底层原理有助于开发者更准确地指导AI生成代码,避免生成低效或错误的实现。选对数据结构比编写“聪明”的代码更重要,O(n²)与O(n)在数据量增大时差距巨大。
以上就是Python列表去重的20种实现方式的详细内容,更多关于Python列表去重、性能优化及算法实践的资料请关注本站其它相关文章!
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













