为了去个重,我差点把列表顺序搞没了!Python“顺序去重”的血泪史
大家好啊,我是你们喜欢瞎折腾代码的那个谁。
事情是这样的,前两天我在处理一坨用户行为日志的数据。需求很简单,就是把一堆用户ID去个重,但是呢,顺序千万不能乱,因为ID的顺序代表了用户操作的时间线。我当时想都没想,抬手就是一行:
user_ids = [102, 101, 102, 103, 101, 104, 105]
clean_ids = list(set(user_ids))
print(clean_ids)结果输出一看,我人傻了:[101, 102, 103, 104, 105]。顺序全给我打乱了!虽然id是去重了,但这玩意儿传到下一个流程里,领导一看,时间线都对不上,不得把我叼飞?
今天就聊聊Python里这个看似简单、实则暗藏杀机的列表顺序去重问题。全是干货,而且是那种你收藏了下次遇到直接复制就能用的那种。
先甩结论:90%的情况用这一行代码就够了
如果你用的是 Python 3.7+ (现在应该没人用3.6以下的了吧?),而且列表里的元素都是简单的数字、字符串这种可哈希的类型,啥都别想,直接用这行神代码:
user_ids = [102, 101, 102, 103, 101, 104, 105]
clean_ids = list(dict.fromkeys(user_ids))
print(clean_ids) # 输出: [102, 101, 103, 104, 105]看到没?102 在最前面,101 在第二位,原来啥顺序,去重后还是啥顺序
原理其实特简单,从Python 3.7开始,字典会记住你插入键的顺序 -。dict.fromkeys()会用列表的元素当作字典的键,自动把重复的过滤掉,最后再把这个有序字典的键转回列表。完美!
如果你还差点踩坑:不可哈希的“脏数据”
但是!生活总是充满惊喜(惊吓)。我昨天遇到的情况就没那么简单。我那个列表里存的不是单纯的ID,而是字典!
比如这种,每个元素是个字典
log_data = [
{"user_id": 102, "action": "click"},
{"user_id": 101, "action": "view"},
{"user_id": 102, "action": "click"}, # 这玩意儿要删掉
{"user_id": 103, "action": "scroll"},
]这时候如果你还想用 dict.fromkeys(),Python解释器直接报错给你看:TypeError: unhashable type: 'dict' -咋整?手动撸一个循环吧。
def deduplicate_dicts(data_list):
seen = set()
result = []
for item in data_list:把字典转成字符串或者frozenset,这样才能放到set里比较简单点,如果字典结构简单,就转成json字符串
item_key = json.dumps(item, sort_keys=True) # sort_keys保证键的顺序一致
if item_key not in seen:
seen.add(item_key)
result.append(item)
return result调用
clean_log = deduplicate_dicts(log_data)
print(clean_log)输出: [{"user_id": 102, "action": "click"}, {"user_id": 101, "action": "view"}, {"user_id": 103, "action": "scroll"}]
你看,第二个重复的 102 click 就被干掉了,而且顺序保留了第一个出现的位置 -
再来点硬核的:海量数据怎么办?
如果你处理的不是上面那种几百条的数据,而是几百万行的日志,内存直接给你干爆。
这时候就得用生成器了。生成器就像个抠门的地主,你要一点,他给你一点,绝不提前多给。
def unique_generator(file_path):
seen = set()
with open(file_path, 'r') as f:
for line in f:
line = line.strip()
if line not in seen:
seen.add(line)
yield line # 用yield,每次只吐一行使用的时候
for unique_line in unique_generator('huge_log.txt'):在这里一条一条处理,内存稳稳的
print(unique_line)这种流式处理的方式,别说百万数据,就是TB级的文件也能轻松应对 -
性能党看过来
有人纠结到底哪个快?我做了一点点小测试(当然没人家专业网站那么严谨),结论是:
list(dict.fromkeys()):代码最优雅,速度极快,首选 -手写 for 循环 + set:其实更快!因为少了一层字典转换的开销,代码可读性也高 -这才是性能王者,而且简单易懂
seen = set()
result = []
for item in huge_list:
if item not in seen:
seen.add(item)
result.append(item)列表推导式那种骚操作:虽然写起来显得很牛逼,但可读性差点,性能也没啥提升,不推荐装这个逼。
总结一下
为了去个重,真的没必要像我最早那样直接用 set() 把顺序丢了。记住这三点:
简单数据,要保序:list(dict.fromkeys(your_list)) 或者手写循环+set -
列表里有字典/列表:手动遍历,把元素转成可哈希的形式(如json字符串)再判断 -
数据量大到爆内存:用生成器,流式处理
好了,今天的坑我就帮你踩平了。下次去重的时候,别再丢顺序了,不然产品经理提刀来找你,别说我没提醒你!
有啥问题评论区见,我虽然不一定秒回,但看到了一定回。
本文涉及AI创作
内容由AI创作,请仔细甄别