news 2026/11/1 4:17:44

列表去重全攻略:从5种基础写法到7个实战细节

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
列表去重全攻略:从5种基础写法到7个实战细节

“5-7列表去重”这个题目,乍一看像是某个培训课程里的章节编号,但我更多把它理解成一次从入门到进阶的梳理——从最基础的5种去重写法,到实战中必须掌握的7个性能与场景细节。列表去重这个操作,几乎是所有写代码的人都绕不开的日常:Python里有列表(list),JavaScript里有数组(Array),SQL里有查询结果集,连Excel下拉列表的去重都能算半个。但就是这么一个看似“一个set就搞定”的小功能,真到了生产环境、笔试现场、数据处理任务里,坑多得超出想象。这篇文章我就把列表去重这件事从原理到实操、从Python到JavaScript再到SQL,完整掰开揉碎讲一遍,适合刚入门想打牢基础的新手,也适合写了大半年代码但没系统整理过去重方案的进阶读者。

1. 去重这个需求到底难在哪:先看清问题的本质

1.1 你以为的去重,和实际遇到的去重不是一回事

先说个我自己的经历。早年我在一个数据清洗的脚本里写过去重,当时数据量不大,两三千条,我直接用了set(),代码跑得飞快,心里还挺美。后来项目上了量,几十万行用户行为日志要按设备ID去重,set一上去内存直接爆了,服务器差点被搞挂。那次之后我才意识到,去重这件事,表面上看是“把重复的删掉”,但实际拆开来看,至少包含四个维度:去重的对象是什么类型(字符串、数字、字典、对象)、要不要保留原始顺序、数据量有多大(内存能不能扛住)、去重后还要不要保留最后一条而不是第一条。

如果只是简单值去重,比如列表里全是整数或字符串,那确实没什么好讲的,set一下完事。但面试和实战里真正考的是另外三种情况:对象(字典)去重、大数据量去重、以及去重后排序规则的定义。这几类问题,光靠set是解决不了的,你得自己定义“唯一性”的标准。举个例子,用户表里有两条记录,姓名相同但ID不同,到底算不算重复?这取决于业务需求,而不只是编程技巧。

1.2 为什么“唯一性标准”是去重的灵魂

很多新手写去重代码容易犯一个毛病:用肉眼觉得“这两条好像一样”,然后在代码里就去掉了。但程序不会看“好像”,它只会按照你给定的规则去判断。所以写去重之前,先回答三个问题:

  • 哪些字段相同就判定为重复?(比如手机号、身份证号、还是姓名+地址的组合)
  • 如果重复了,保留哪一条?(第一条、最后一条、还是某个字段值最大的那条)
  • 去重后的顺序有什么要求吗?(保持原顺序、按某个字段排序)

我把这三点称为“去重三问”。任何一次去重操作,只要把这三问答清楚了,代码怎么写其实已经定了一半。剩下的就是选择合适的数据结构和算法。我见过不少线上事故,就是因为没想清楚第二问——保留哪条。比如在消息推送场景里,同一个用户短期内收到了多条重复通知,系统要做去重,如果只保留最早的那条,用户就可能错过最新的消息内容;如果保留最后一条,就要求遍历方向得反过来。这种细节,比set还是dict哪个快更重要。

2. 基础玩法盘点:5种最常见列表去重写法及原理

2.1 用集合set去重:最快但别忽略顺序问题

先聊最经典的set去重,代码短、性能好,但有个天生缺陷:不保证顺序。Python里set是基于哈希表实现的,存储时不关心你把数据放进去的顺序。所以在Python 3.7之前,set去重后的列表顺序完全是随机的;3.7之后虽然dict有序了,但set本身依然无序。JavaScript这边的Set对象稍微好一点,它按照插入顺序迭代,所以在JS里[...new Set(arr)]能保持顺序。同样是set,两种语言行为还不一样,这就能看出跨语言经验的重要性。

# Python 基础set去重 data = [3, 1, 2, 3, 1, 4, 5, 2] unique_data = list(set(data)) print(unique_data) # 结果不保证顺序,可能是 [1, 2, 3, 4, 5] 的任意排列

如果你想用set去重又必须保持顺序,那就得配合列表推导式或者循环来搭一个“seen集合”:

# 保持顺序的set辅助去重 seen = set() result = [] for item in data: if item not in seen: seen.add(item) result.append(item) print(result) # [3, 1, 2, 4, 5],顺序保留

这块值得展开说。set底层是无序哈希表,它的查找是O(1)的,所以这个“seen集合+遍历”的写法,时间上依然很快,空间上多了一个set的开销。这也是笔试里最常考的解法之一,面试官看到你能答出“set能达到O(1)查找,再配合遍历保持顺序”就已经和普通选手拉开差距了。

2.2 字典dict.fromkeys去重:兼顾顺序与稳定性的老古董

在Python里,还有一个老牌写法:dict.fromkeys()。它的逻辑是把列表元素作为字典的键,利用字典键唯一的特性去重。在Python 3.7+里,字典保持插入顺序,所以这个方法天然稳定。

data = ["apple", "banana", "apple", "orange", "banana"] result = list(dict.fromkeys(data)) print(result) # ['apple', 'banana', 'orange']

为什么不用set而用dict.fromkeys?因为旧版Python里set无序,而dict虽然也是哈希表,但在3.7之后却记录插入顺序(实质上是链表+哈希索引的实现)。所以在“去重+保序”这个组合需求下,dict.fromkeys是一个不用写循环的优雅方案。对于JavaScript开发者来说,这个思路对应的是手动维护一个Map或Set对象,因为JS没有直接的fromKeys数组方法,但Map的插入顺序特性是一样的。

2.3 遍历+新列表手工去重:理解力第一,性能第二

当你面对的是一个复杂对象数组(Python里是字典列表,JS里是对象数组)时,set和dict.fromkeys就不太好使了,因为列表里的每个字典本身是不能哈希的(unhashable)。这时候最朴素也最灵活的办法就是遍历,自己维护一个“唯一性标记”。我见过很多教程说遍历效率低,但其实在数据量不大的时候,遍历反而是最直观、最好维护的方案。比如:

data = [ {"name": "张三", "age": 20}, {"name": "李四", "age": 22}, {"name": "张三", "age": 30}, ] seen = set() result = [] for item in data: key = item["name"] # 按姓名去重 if key not in seen: seen.add(key) result.append(item) print(result) # 输出第一条张三,而非30岁的张三

这类写法终极优势在于,你可以通过把key定义成任何组合(比如(item["name"], item["age"]))来满足业务上的“唯一性标准”。代价是代码多几行,但可读性极高,别人一看就懂你按什么规则去重。团队协作时,这种可读性比省几毫秒宝贵得多。

2.4 排序+相邻比对去重:不依赖哈希结构的方法

还有一种掌握频率较低但值得会的思路:先排序,再相邻比对,遇到相同的就跳过。这在C语言这类没有内置Set结构的场景更常见,但在Python、JavaScript里也能用。它的原理非常简单:排序后,重复元素会靠在一起,那你只需要声明一个空结果表,逐一遍历,如果当前元素和上一个不同就加入结果。

data = [5, 3, 1, 3, 5, 2, 1] data.sort() # [1, 1, 2, 3, 3, 5, 5] result = [] for i in range(len(data)): if i == 0 or data[i] != data[i - 1]: result.append(data[i]) print(result) # [1, 2, 3, 5]

这个方案的时间复杂度是O(n log n),主要花在排序上。数据量很大但内存受限(比如嵌入式环境)时,它可以节省额外set的空间开销,因为除了排序占用的存储,你甚至不需要保存“已见过的元素”。代价是原始顺序丢失。如果你能灵活回答出这种方案和set方案的取舍,在技术面试里会显得底层功底很扎实。

2.5 一行式的filter+index高级玩法:炫技但需要小心

在JavaScript里,有一种很时髦的金句写法:

const arr = [1, 2, 1, 3, 2, 4]; const unique = arr.filter((value, index) => arr.indexOf(value) === index); console.log(unique); // [1, 2, 3, 4]

这个逻辑其实就是“如果当前元素第一次出现的位置就是当前位置,说明它此前没出现过”。代码看起来极度简洁,面试时写出来确实加分,但它的性能是O(n²)的:arr.indexOf要遍历整个数组才能知道value第一次出现在哪,所以体内嵌套了一层查找。数据量一旦超过几千,体感就会很明显。我自己的经验是:代码题、教学演示可以用,但生产环境尽量别用这个写法。真正在生产环境里推荐的JavaScript去重写法是:

const unique = [...new Set(arr)];

或者用Map处理带对象属性的去重:

const objArr = [ { id: 1, name: "a" }, { id: 2, name: "b" }, { id: 1, name: "c" }, ]; const seen = new Map(); const uniqueObj = objArr.filter(item => { if (!seen.has(item.id)) { seen.set(item.id, item); return true; } return false; }); console.log(uniqueObj); // [{ id: 1, name: "a" }, { id: 2, name: "b" }]

3. 进阶硬核场景:7个你必须掌握的实战细节

3.1 对象数组按多个字段去重:把Key变成元组

热搜词里“对象数组去重”反复出现,说明这是大家在实际中最大的痛点之一。对象数组去重的核心就是定义一个“合成key”。在Python中可以用元组,在JavaScript中可以用JSON.stringify把多个字段拼成字符串,或者用反引号模板拼接。举个例子,电商订单按“用户ID + 商品ID”去重:

orders = [ {"user_id": 1, "product_id": 101, "price": 9.9}, {"user_id": 1, "product_id": 101, "price": 8.8}, {"user_id": 2, "product_id": 101, "price": 9.9}, ] seen = set() result = [] for order in orders: key = (order["user_id"], order["product_id"]) if key not in seen: seen.add(key) result.append(order) print(result) # 保留两条,重复订单中的8.8被去掉

这里有个值得说的点:在这个数组里,去重后到底保留9.9还是8.8?按照上面的写法,保留的是9.9,因为它是第一条出现的。但我见过很多业务场景要求保留“最新的一条”,也就是后面出现的8.8(假设后出现的才是改价后的新数据)。如果是那样,遍历方向就得反过来,从列表末尾往前遍历,或者直接用新数据覆盖seen对应的旧记录。这个细节非常容易忽略,但直接影响业务正确性。我建议每个开发者在写去重前养成本能:先确认保留哪一条。

3.2 大数据量去重的内存优化:用布隆过滤器降低内存占用

这是“7个细节”里含金量最高的一个。当列表量达到千万级甚至亿级,你不可能用一个set把全部元素展开。比如一百亿个用户ID,每个ID算16字节,set还会附带哈希表的额外存储开销,内存可能是原始数据的3~5倍。实际扛不住。业内常用方案是分治加哈希(先把数据按照哈希值分到多个文件,每个文件内单独去重),或者用布隆过滤器(Bloom Filter)这种概率型数据结构,先过滤掉绝大多数重复项,少量误判再回源数据库复核。

我没法在这里给你写出工业级完整实现,但可以给一个通用思路:

  • 如果数据在数据库里,用SQL的DISTINCT或GROUP BY交给数据库引擎处理,别取到内存里再排。
  • 如果数据是日志文件,流式读取,边读边写,设置一个大小可控的seen集合,满了就把已去重结果落盘,再清空set。
  • 如果业务允许极低比例的误判,用布隆过滤器做第一层过滤,剩余候选再精确去重。

网上有个很形象的比喻:用set去重就像把所有东西搬进屋再分类;用布隆过滤器去重就像是先在大门口问一句“这东西来过没有”,只有似是而非的人才进屋细查。这套方法论在处理亿级设备ID、日志清洗时非常管用。

3.3 SQL层面的去重查询:DISTINCT和GROUP BY怎么选

热搜词里“sql语句去重”、“sql语句去重查询”出现了好几次,我单独拿一节说。SQL去重最常见的是两种写法:

-- 方式一:DISTINCT SELECT DISTINCT user_id FROM orders; -- 方式二:GROUP BY SELECT user_id FROM orders GROUP BY user_id;

很多人以为这两种写法完全等价,其实差别很大。DISTINCT的语义就是把重复行去掉,写法最简单;GROUP BY本质是分组聚合,它的能力远不止去重,你可以在后面接COUNT、SUM、MAX等聚合函数。举个例子,如果你想看每个用户下了多少单,就只能用GROUP BY。另外,如果你只想去重但还想带出其他字段,DISTINCT会把所有选中的字段组合当成一个整体来去重,而GROUP BY搭配任意值函数的写法各不相同,这在不同的数据库(MySQL、SQL Server、PostgreSQL)里还有细微差别,很容易踩坑。

有一个更刁钻的需求:按某个字段去重,但返回整行记录。这种事情SQL标准里没有一条“去重保留一条”的语法直接搞定,常见的写法是窗口函数ROW_NUMBER():

-- 按user_id去重,每个user_id保留最新一条订单 SELECT * FROM ( SELECT *, ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY create_time DESC) AS rn FROM orders ) t WHERE t.rn = 1;

这套写法在SQL Server和PostgreSQL里直接支持,MySQL 8.0+也支持窗口函数了,但如果你还在用MySQL 5.7,就得换成“自连接取最大ID”之类的经典方案:

SELECT o.* FROM orders o JOIN ( SELECT user_id, MAX(id) AS max_id FROM orders GROUP BY user_id ) t ON o.id = t.max_id;

这里顺带回应热搜词里出现的“mssql 去重 多表查询”。多表查询去重时,一定要先想清楚:重复是因为表本身有重复数据,还是因为JOIN造成的笛卡尔积膨胀?如果是JOIN造成的,去重是治标不治本,正确的做法是先按维度字段把子查询缩小范围再JOIN,或者用EXISTS替代JOIN。

3.4 保持顺序与去重的优先级问题

我觉得很值得强调的一个点:很多人写去重时没意识到“去重”和“排序”其实是两件独立的事。“去重”负责的是剔除重复项,“排序”负责的是按规则调整顺序。但这两件事经常被混在一起。比如一个运营后台要展示“最近活跃用户”,要求去掉重复用户且按最近活跃时间排序。有的新手会把去重后的结果再排序,结果发现保留哪条取决于去重时机,顺序就乱了。

正确做法是:先按业务规则排好序,确保“要保留的那条”出现在合适的位置,然后再去重。比如你想保留每个用户最近一条登录记录,那应该先按登录时间倒序排列,然后用seen集合去重。这样保留下来的,天然就是最近一条。这个顺序问题,我在实际项目里至少踩过三次,每次都修了半天,最后发现是把先排序还是先去重的因果关系搞反了。

3.5 列表切片与去重组合使用的妙处

方向了,顺便提一下热搜里的“列表切片”。切片常配合去重来做“分段处理”,比如一个大列表没办法一次性加载到内存,就可以分片:

data = list(range(1000000)) chunk_size = 100000 seen = set() result = [] for i in range(0, len(data), chunk_size): chunk = data[i:i + chunk_size] for item in chunk: if item not in seen: seen.add(item) result.append(item)

或者当你只需要处理列表的一部分时,先切片再去重,比全量去重高效得多:

top_100 = data[:100] unique_top_100 = list(dict.fromkeys(top_100))

Python的切片返回的是新列表,会复制一份数据,所以切片的开销也不是零。但好处是很灵活。JavaScript对应的方法是slice()或者splice()(后者会修改原数组,慎用)。这个组合技巧在“分页去重”或“抽样去重”场景很实用。

3.6 数据清洗中的“脏数据”去重:空值与空白字符

真正的数据清洗,去重前往往还要加一道“规整化”。你在热搜里也能看到“清洗---sql语句去重”的说法,可见清洗和去重是天然绑定的。常见的脏数据包括:

  • 同一个字符串,后面带了一个空格,比如"张三 "和"张三"被判定为不同。
  • 全角半角数字混搭,如"100"(全角)和"100"。
  • None、NaN、空字符串这些特殊值,它们是否算重复?

排查时,最稳的命令是把数据的repr形式打印出来,或者用正则对关键字段做strip和replace。Python里:item.strip()。JavaScript里:item.trim()。然后再去重。如果直接对脏数据去重,结果列表里依然会有“看起来重复,但程序认为不重复”的条目。这才是数据清洗里最隐蔽的坑。

3.7 去重后的统计与可视化:别只盯着删重复

去重不只是“删数据”,在数据分析中它往往是统计的准备工作。比如统计独立访客数(去重后的用户数)、独立设备数。这个场景里你去重后还要对结果继续做聚合、排序、分组。我常见的套路是先把去重结果存成临时表或者变量,再基于这个结果继续查。比如:

WITH unique_users AS ( SELECT DISTINCT user_id FROM visits ) SELECT COUNT(*) FROM unique_users;

在Python里同样,去重后的result列表可以再传给Pandas的DataFrame做后续分析。记住,去重不会是你任务的终点,它只是让后续的数据口径更准确。

4. 跨语言视角:同样的问题,不同语言的“最优解”差异很大

4.1 Python与JavaScript去重写法对比

很多时候大家会纠结学Python还是JavaScript,其实对照着看同一算法在不同语言的表达,反而理解最快。我先列一张小表:

语言简单类型去重对象数组按字段去重保序方式
Pythonlist(set(data))遍历+seen集合,key用元组dict.fromkeys()
JavaScript[...new Set(arr)]filter+MapMap天然保序
SQLSELECT DISTINCTGROUP BY + ROW_NUMBERORDER BY

你看,语言是外衣,核心思想都绕不开“哈希表、排序、遍历”。写Python的人觉得set爽,写JavaScript的人觉得Set也爽,但当你处理对象数组时,两边的写法都回归到了“自定义一个唯一key”。所以我一直建议初学者别只盯着某个语言死记硬背API,把“唯一性标准”想透,什么语言都一样。

4.2 影刀RPA列表去重里常见的小剧场

热搜词里有一条“影刀rpa如何将列表中的[]去掉”,这个场景很生活。很多人用影刀抓取网页内容,抓到的列表元素里带着中括号或者多余的空格,比如[item1, item2]这样。其实那不是列表本身的格式问题,而是你把列表转成字符串展示了。真正的处理办法是:遍历列表,对每个元素strip清洗,然后用列表推导式过滤掉空字符串。

# 伪代码示意 clean_list = [str(item).replace("[", "").replace("]", "").strip() for item in raw_list]

其实和去重无关,但属于“对列表进行规整化处理”的常见需求。如果你在影刀里需要去重:直接用影刀提供的“列表去重”指令也行,或者自己写循环+seen集合。影刀的Python环境支持大部分Python语法,所以上面的代码基本能平移。

4.3 Excel下拉列表的“去重”到底在去什么

热搜词“excel设置为下拉列表”和“怎么把下拉列表同步到整列”也很有意思。Excel里创建下拉列表时,数据源如果包含重复项,下拉选项里会重复显示,很丑。一般操作是:先对源数据做一次“删除重复值”(Excel数据选项卡里有现成功能),或者用公式提取唯一值。这个场景里的“去重”和编程里的list去重逻辑完全一致,但处理工具不同。你要是Excel老手,也可以直接用“数据透视表”拉一列所有唯一值,然后把这列作为下拉数据源。你看,去重思想到处都在。

5. 常见问题与排查技巧实录

5.1 为什么用set去重后结果顺序每次都不一样

这是初学者问得最多的问题。原因我在前面已经提过:set的存储顺序由哈希表决定,并不依赖插入顺序。Python里不同版本甚至同一版本不同运行环境下,哈希种子(hash seed)可能不同(Python出于安全考虑给字符串哈希引入了随机盐值),所以每次运行顺序可能不同。解决方案也很简单:去重要保序就别直接list(set(data)),用dict.fromkeys或循环+seen。如果你用到JavaScript的Set,倒是不用担心,因为Set的遍历顺序就是插入顺序。这算两种语言一个小而关键的差异。

5.2 为什么去重后还是能看到“重复”数据

一般三种可能:

  • 你把浮点数里的精度问题忽略了。比如1.0和1.00在程序里通常相等,但0.1+0.2和0.3则不相等(浮点数精度问题)。
  • 数据是先转字符串再落地的,导致"1"和"01"被当成不同元素。
  • 数据库中的CHAR类型自动补空格,导致"abc"和"abc "看似相同,实际不同。

排查方式:先打印每个元素的repr,肉眼验证。然后尝试归一化:数字统一转成decimal,字符串统一strip,日期统一格式。只有源数据规整了,去重才有意义。

5.3 内存不足:set存不下几千万条数据怎么办

这个问题在第二个大章节里提了布隆过滤器,这里再补充一个粗粒度但非常实用的方案:分而治之。把大列表拆成若干条小文件(按某个hash值取模分桶),内存里维护的seen集合只负责处理当前桶。每个桶去重完,结果合并。因为同一个重复项目的哈希值一定落在同一个桶里,所以不会漏掉重复。这个桶的合并是安全的。你甚至可以先用shell命令对文件按第一个字段排序,再用相邻比对去重,本质上就是第2章里的“排序+相邻比对”方案的分布式版本。

5.4 对象数组去重时提示unhashable type: 'dict'

Python新手常见报错:

list(set(data_dict_list)) # TypeError: unhashable type: 'dict'

原因很简单:字典是可变的,不能直接哈希。怎么解决?第一种方案:如果你只是想按某个字段去重,用seen集合只存那个字段的值,而不是整个字典;第二种方案:如果确实要整体判断字典是否重复,可以先把字典转成JSON字符串再放进set(但要注意键的顺序问题,最好用json.dumps的sort_keys=True)。不过第二种方案有很多边缘情况,比如嵌套结构、浮点数精度等,复杂业务不建议。

5.5 维护性角度:去重逻辑要不要封装成函数

我自己的建议是:凡是去重规则超过一行,就封装成函数,且函数名里带上“按什么去重、保留哪条”这些语义。比如:

def dedupe_by_key(items, key_func, keep="first"): seen = set() result = [] iterable = items if keep == "first" else reversed(items) for item in iterable: key = key_func(item) if key not in seen: seen.add(key) result.append(item) result.reverse() if keep == "last" else result return result

这样,调用方一看就懂。生产代码里最怕的开发模式是:把去重代码临时写在某个报表脚本里,谁都看不懂它按什么规则剔除的重复项。结果三个月后别人改需求,误删了一堆数据。封装函数,注释写明规则,是让项目长期可维护的基本操守。

5.6 关于不同语言里“==”和“is”对去重的影响

这个坑比较硬核,但值得说。Python里,两个结构相同但内存地址不同的列表,用==判断是相等的(内容比较),但is判断是不相等的(身份比较)。在去重时,如果你自己实现了对象数组的相等性判断,容易把这两者搞混。JavaScript里,两个相同内容的对象用==也是不相等的,因为对象比较的是引用。所以对象去重一定要自己定义key,而不是指望编程语言内置的对象相等。这也是为什么set无法直接处理对象列表的根本原因:语言层面的“相等”不一定是业务上的“重复”。

6. 一个完整的项目实操案例:从零写一个带性能考量的通用去重模块

6.1 需求定义

假设有这么个任务:有一个千万级订单日志的JSON数组,每条记录里有order_id(字符串)、user_id(数字)、product_id(字符串)、created_at(时间戳)。要求按user_id + product_id去重,每个组合保留created_at最大的那条记录,最后输出保序结果。这是一个相当典型的实战任务,既涉及去重三问的标准答案(唯一性标准是user_id+product_id,保留哪条是created_at最大,顺序是原顺序),又有性能和数据结构考量。

6.2 分步实现

第一步,读取数据(这里简化成列表推导式)。

第二步,明确遍历方向:因为要保留created_at最大,最稳的办法是同样key先比较时间戳再决定,而不是简单逆序。

第三步,用字典来维护每个key对应的记录,如果新记录的created_at更大,则覆盖。

def dedupe_keep_latest(items, key_func, time_func): best_records = {} for item in items: key = key_func(item) if key not in best_records or time_func(item) > time_func(best_records[key]): best_records[key] = item return list(best_records.values())

第四步,性能检查:这里使用了字典做hash存储,时间O(n),空间O(n),在千万级数据上内存肯定撑不住,所以实际生产环境要配合分桶方案。但思路就是这个思路——保留最大值用“覆盖”策略,比“先排序再去重”更清晰。

我在实测里遇到过这个模块的两种bug。第一种:time_func比较的是字符串,导致"1577836800"被当成时间戳比较,全是乱的,改成int再比就正常了。第二种:key_func里字段存在缺失值,比如product_id为空字符串,结果所有空商品ID的用户组合都变成同一条,去重过头了,业务上要求空值不算有效组合,于是加了个条件过滤。这类问题你在单元测试里很难发现,必须是在真实数据上跑一遍才能暴露。

6.3 测试与验证

验证去重逻辑对不对,我有一套实操经验:先手动构造5-6条边界数据(含同key不同时间、不同key相同时间、空字段等),用assert逐个验证。别等代码跑完了再看结果。比如:

test_data = [ {"user_id": 1, "product_id": "A", "created_at": 100}, {"user_id": 1, "product_id": "A", "created_at": 200}, {"user_id": 1, "product_id": "B", "created_at": 300}, ] assert dedupe_keep_latest(test_data, lambda x: (x["user_id"], x["product_id"]), lambda x: x["created_at"]) == [ {"user_id": 1, "product_id": "A", "created_at": 200}, {"user_id": 1, "product_id": "B", "created_at": 300}, ]

测试通过了,再去跑真实数据。千万别小看这段基础测试,它能省去你后面定位问题的大量时间。

7. 我在实际项目中积累的一些心得

去重这个功能,我在无数个项目里写过,踩过的坑比写过的set多得多。

最想说的是:不要把去重当成一个独立的编程步骤,而应该当成数据治理的一部分。去重前先想清楚数据怎么来的、重复是怎么产生的——如果重复是上游接口重复推送导致的,光靠下游去重只是亡羊补牢,更根治的办法是在源头加幂等控制。如果重复是因为数据库表设计没有唯一索引,更合理的解决是给表加联合唯一索引,让数据库物理层面杜绝重复。程序里去重永远是兜底方案。

第二点心得和“保留哪条”相关。凡是要保留“最新一条”的场景,我建议在代码里明确写出比较逻辑,而不是依靠列表顺序。因为列表顺序在不同数据源里不可靠,你可能从数据库查出来是自然序,从消息队列消费出来又变成了乱序。唯一的保底手段就是按时间字段自己比较。

第三点心得对比工具效率:能交给SQL的去重尽量交给SQL。数据库的优化器配合索引,处理亿级数据去重是它就吃饭的本事,你把这些数据拉出来在脚本里set,简直就是暴殄天物。我见过好些人写个Python脚本,从MySQL里select出来一万行,到内存里去重,转了一圈又写回去。其实直接在SQL里一条GROUP BY就全干完了,还能少写很长的判空代码。说白了,去重不只是“算法”,它还是“架构问题”——在底层去重,还是在上层去重,性能差别天壤地别。

关于“5-7列表去重”——我猜这个题目可能来源于某本教材的章节号,也可能读者就是想把5种基础方法到7个进阶技巧连起来看。不管是哪种,我希望这篇梳理能帮到你:先掌握好set和字典/Map的基本写法,再把这些写法迁移到对象数组、SQL、甚至Excel里。把这个技术动作想透,你之后写任何代码都会少走一点弯路。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/25 16:12:36

S/4HANA嵌入式EWM库存查询Bug排查:从LU队列到报表口径

前阵子项目群里甩过来一张截图,报障标题就一句话:“S4 2023 EWM库存查询bug”。截图里是仓库同事用 LT0W 查一个电子料号,可用库存显示 1200,可实物早就发完了。业务的第一反应是把问题推给开发:“查询的数不对&#x…

作者头像 李华
网站建设 2026/10/24 18:12:32

2026论文降重工具实测:10款横评与免费降重路线全解析

每年春夏之交,打开毕业论文查重报告的那一刻,几乎是我读研生涯里最刺激的时刻。我初稿查重32.6%,红线从综述一路延伸到结论,后来花了整整两周反复修改才压到7.8%。这期间我从免费的论文降重工具一路试到按字数收费的高级版&#x…

作者头像 李华
网站建设 2026/10/26 23:59:53

继续教育论文降AI率实测:千笔与学术猹对比

这两年继续教育的论文要求,真是越来越硬核了。以前查重过了就万事大吉,现在还得过AIGC检测,而且是知网、万方各自有各自的判定逻辑。很多在职学员本身时间紧、底子薄、思路又依赖AI,结果一提交就被标注“疑似AI生成”,…

作者头像 李华
网站建设 2026/10/28 8:24:17

UGUI使用小结:从Canvas到ScrollRect的性能优化与实战

1. 初步内容框架与写作思路1.1 整体定位与写作目标这篇"UGUI 使用小结"不是给刚打开 Unity 的新手看的入门教程,也不是把官方文档翻译一遍的笔记。我打算写成一份面向已经能用 UGUI 搭出简单界面、但总感觉哪里不对劲的开发者——比如 Canvas 为什么要分三…

作者头像 李华
网站建设 2026/10/27 23:37:22

Java+MySQL图书馆管理系统:环境配置、数据库设计与避坑指南

简介:基于Java与MySQL的图书馆信息管理系统完整项目,专为需要完成期末大作业、课程设计或毕业设计的学生准备,可直接作为高分参考方案。资源包共152个文件、11.79MB,包含19个Java核心后端类、14个Vue页面组件、15个CSS样式表、33个…

作者头像 李华
网站建设 2026/10/26 20:31:00

基于MCP协议构建聊天机器人Tool Server:架构设计与工具调用实践

1. 从聊天窗口到工具网关:一个聊天机器人的角色跃迁最开始做这个聊天机器人,纯粹是想给自己省点事。每天在终端和浏览器之间来回切换,查文档、跑脚本、翻日志,重复劳动太多。于是我用 Python 写了个简单的对话循环,接上…

作者头像 李华