Ruby实现filter_out!方法时为什么需要调用uniq?
Ruby filter_out! 方法实现疑问解答
1. 直接使用arr.each无法得到预期结果的原因
Ruby的Array#each是按索引顺序遍历原数组的,迭代过程中如果修改原数组的长度(比如删除元素),会导致后续元素的索引整体前移,出现漏遍历的问题。
以你给出的第二个示例做执行拆解:
arr_3 = [10, 6, 3, 2, 5 ] # 直接遍历原数组删除偶数 arr_3.each { |ele| arr_3.delete(ele) if ele.even? }
执行逻辑:
- 初始数组为
[10,6,3,2,5],长度为5,首先取索引0的元素10,符合删除条件,删除后数组变为[6,3,2,5] - each继续取索引1的元素,拿到的是3(原索引1的6已经前移到索引0的位置,被直接跳过),不符合删除条件
- 继续取索引2的元素,拿到的是2,符合删除条件,删除后数组变为
[6,3,5] - 继续取索引3的元素,当前数组长度只有3,索引超出范围,遍历结束,最终得到错误结果
[6,3,5]
2. 调用uniq后运行正常的原因
arr.uniq会返回一个完全独立于原数组的新数组,你后续的each遍历的是这个新生成的去重数组,迭代过程完全不受原数组修改的影响,不会出现索引偏移的漏遍历问题。
你之前猜测的「uniq会使用proc的返回值做比较」是误解,此处uniq的作用仅仅是生成独立的遍历副本,就算替换成
arr.dup.each(直接复制原数组生成副本遍历),运行效果完全一致。
另外你的实现中用到的arr.delete(ele)本身就会删除原数组中所有等于ele的元素,所以即使遍历的是去重后的元素列表,也能把原数组中所有符合条件的元素一次性删除,刚好匹配需求。
更稳妥的实现方案
当前依赖uniq的实现仅适用于proc判断逻辑仅和元素值相关的场景,如果元素是自定义对象、或存在值相同但proc判断结果不同的特殊场景,会有隐藏问题。更通用的实现可以参考:
def filter_out!(arr, &prc) # 先筛选出所有要保留的元素,再直接覆盖原数组内容 keep_elements = arr.select { |ele| !prc.call(ele) } arr.replace(keep_elements) end
内容的提问来源于stack exchange,提问作者Zenibo
相关产品推荐
相关产品推荐

