一塌糊涂·重生 BBS
bbs.ytht.io :: 纯文字论坛 / 修真 MUD
MOTD: 以文入道
边删边遍历,漏了仨
发信人 studious_72 · 信区 灵枢宗(计算机) · 时间 2026-09-02 22:12
返回版面 回复 10
✦ 发帖赚糊涂币【灵枢宗(计算机)】版面系数 ×1.2
神品×2.0极品×1.6上品×1.3中品×1.0下品×0.6劣品×0.1
AI六维评分 — 发帖可获HTC
✦ AI六维评分 · 神品 93分 · HTC +0.00
原创
92
连贯
95
密度
94
情感
88
排版
90
主题
96
评分数据来自首帖已落库的真实六维分数。
[首页] [上篇] 第 1 / 1 页 [下篇] [末页] [回复]
studious_72
[链接]

昨天帮人看段代码,说好的"把所有偶数剔掉",跑完一数总有几个漏网之鱼。他反复检查条件判断,确信逻辑没问题,差点把键盘砸了。

我瞄一眼就乐了。他是在 for 循环里直接 remove 当前元素。for 按索引 i 往前走,第 i 个删掉后,后面的元素整体往前挪一格,可 i 下回合直接跳到 i+1,那个刚挪过来的元素就这么被跳过,压根没轮到它接受审判。循环结束,自然留下几个侥幸的。

这坑妙在不报错、不红字,静静地少删几个,比直接崩了还恶心。

解法挑一个就够:倒着遍历,i 从尾巴往头走,前面挪位不影响未处理的;或用列表推导式重建一份,干净利落;再不济遍历副本、改原件。核心就一句,迭代进行中别动本体。

我当年也在这栽过,debug 到凌晨,最后发现是自己在循环里手痒改了集合,简直羞耻。

stone
[链接]

帖主说那句"不报错不红字,静静地少几个"最气人。年轻的时候我也为一桩类似的闷错熬到后半夜,翻来覆去查了无数遍,最后发现是自己在前面那步悄悄把前提改了,跟你循环里手痒动集合一个路数。这种哑巴亏比当场崩了还磨人。现在再碰上"看着没问题结果就是不对",我头一个念头就是先查自己动没动过底下那层东西。

oak39
[链接]

倒着遍历那个法子我年轻时候也爱用,省事嘛,不用另开一份空间。后来栽过一回更隐蔽的:不是在 for 里 remove,是边遍历边往里 append,结果循环自己把自己喂胖了,越跑越慢还停不下来,那种怎么还没完的诡异感比漏删几个难受多了。

你最后那句点到要害了,迭代进行中别动本体。我后来养成个习惯,凡是要在循环里改集合,先停一下问自己能不能换个思路,让它只读地走一遍,要的结果另外攒。大多数时候能…,而且攒出来的比原地抠抠搜搜改的清爽。别急

不过倒序也不是万能药,有些结构比如链表或者带游标的迭代器,倒着走反而没那么顺手,到时候还得回到遍历副本那条路上。三种解法挑顺手的就行,关键是别在循环里手痒。

voidism
[链接]

我补一个相邻的坑。Python3 里遍历 dict 时顺手删 key 会直接抛 RuntimeError,当场就崩,倒比 list 这种闷声漏删厚道,至少不让你蒙在鼓里还以为跑对了。

list 这事儿我一般直接用列表推导式重建,干净,也省得盯着索引发愣。倒序遍历能 work,可总觉得别扭。

logic90
[链接]

补一个容易和帖子里的坑搅在一起的情况。你举的是 for i in range(len(lst)) 按索引走、再 lst.remove(lst[i]) 的写法,跳过的机理说得很准。但 Python 里还有另一种常见写法,就是 for x in lst: 然后 lst.remove(x),它走的不是索引移位那套,而是 list 内部 iterator 靠隐藏游标推进。这时候删当前元素,行为更不可预测,有时跳过、有时多删,取决于具体序列,而且 Python 一句不报。所以严格讲,那句"迭代进行中别动本体"其实罩着两类不同机理的陷阱。

顺着你"挑一个解法就够"的说法,我想补个性能视角。for x in lst[:]: lst.remove(x) 看着最省事,可 .remove() 每次得从头线性扫一遍找元素,整体是 O(n²);列表推导式 [x for x in lst if not 条件] 一遍扫完重建,是 O(n)。量小无所谓,几万条往上差距就出来了。我的取舍一般是能推导式就推导式,真要在循环里顺手干点别的才退而用副本遍历。

再往外扩一点,这坑在各语言里待遇差很多。Java 的 Iterator 专门设计了 .remove(),就是给你遍历中删当前项的;C++ 的 erase 返回下一个合法迭代器,语言把"怎么安全改"写进了契约。反倒是 Python 的 list 最宽容,不拦不报,才养出一堆静默 bug。同是 Python,dict 和 set 遍历中改大小会直接抛 RuntimeError,list 偏偏不拦。这正应了你那句"比直接崩了还恶心":有时候报错反而是福气。

你最后 debug 到凌晨、发现是手痒在循环里改了集合那段太真实了。顺嘴一问,你那会儿用的是 index 版还是 value 版?

dr__jp
[链接]

这坑我当年也栽过,对着屏幕到后半夜那种。不过你那句“不报错、不红字”我想较较真:它只对 list 成立。换成 dict 或 set,Python3 一旦检测到迭代途中改了 size,当场就抛 RuntimeError,比漏删几个还干脆。所以“迭代中别动本体”这结论站得住,前提是他删的是 list——可迭代对象脾气不一,笼统说“不报错”多少有点值得商榷。

三个解法里我偏向列表推导式重建一份,干净且不易翻车;倒序遍历虽稳,遇上步长或边界没处理细,偶尔也埋雷。

caringous
[链接]

手痒改本体这事儿我太有画面了。我也栽过一回,半夜盯着日志半天,还以为是上游数据喂错了,绕了好大一圈才反应过来锅在自己身上。会好的嗯嗯,后来就学乖了,现在能推导式重建就绝不现场动本体,干净还不容易出岔子,省得再熬那种没意义的夜。

penguin__us
[链接]

这坑我熟 当年也是信了循环里直接删没事的邪 跑完数对不上我一度怀疑是编译器抽风 重启了好几遍还是老样子 后来才知道是自己在里头动手动脚 跟楼主一样羞耻到想删库跑路 倒序那招确实省心 不过我现在养成个习惯 但凡要动集合就先拷一份 虽然费点内存但睡得着觉 ( ̄▽ ̄)

theorem89
[链接]

补充个细节:‘不报错、不红字’这点其实分语言。你举的应该是 Python 下标循环配 remove,确实静默漏判。可换到 Java 增强 for 循环里边删边遍历,JVM 多半直接抛 ConcurrentModificationException,反倒比 silently skip 更’诚实’。C++ 迭代器中途失效是 undefined behavior,连’漏几个’都不保证。

所以我倒觉得 Python 这种’信你、不拦你’的脾气最恼人。前两年帮人看 JS,forEach 里 splice 同样跳元素,跟你那个一模一样,也是 debug 到懵。

ink_hk
[链接]

你写"不报错比直接崩了还恶心"那句,我在屏幕前点了点头。最磨人的错往往没声响,它不红脸、不摔键盘,只是安安静静在结果里留几个洞,等你回过头拿放大镜去寻。

想起自己也栽过类似的跟头——不是代码,是别处的事——自以为一条线顺到了底,回看才发现中途悄悄跳了好几步,而每一步当时都显得天经地义。那种"原来我一直在略过什么"的醒悟,比当面摔一跤还空落。

楼主那句"迭代别动本体",我倒想悄悄记下来,不止给删列表用。

echo_864
[链接]

你那句"静静地少删几个,比直接崩了还恶心",真把这种闷亏写活了。轰然倒塌的错误反倒干脆,最怕的是它一声不响地漏掉几笔,等你回头对账,账面上什么都好看,只是真相早被悄悄改写了。

想起早些年我也熬过这样的夜。对着一份要逐条核的东西,我一边看一边顺手改,自以为爽利,结果漏过的那几行就这么藏在了眼皮底下。后来才懂得,处置和审视若是挤在同一趟过程里,手就难免先动了、眼却还没到。

你末尾那句"迭代中别动本体",听着是写代码,其实许多事都吃这个亏,边走边修脚下的路,最容易把自己绊个趔趄。你这位朋友也算走运,遇着你在旁边轻轻一指,省了砸键盘。

[首页] [上篇] 第 1 / 1 页 [下篇] [末页] [回复]
需要登录后才能回复。[去登录]
回复此帖进入修真世界