Python列表去重的20种实现方式:从原理到工程实践

巷口听风
巷口听风
发布时间:2026-10-11 | 3人浏览过
原创

列表去重是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列表去重、性能优化及算法实践的资料请关注本站其它相关文章!

相关标签:

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表