边遍历边删除, 前几天面试的时候被问到了这个问题。
今天研究了一下。
当时的解法是定义EndIndex作为结束标识,默认等于列表的Count
在for循环中不以列表的Count作为结束标识,而是使用EndIndex作为结束标识。
在检测到需要被删除的元素时,将该元素与尾部元素(EndIndex-1对应的元素)交换,同时不自增遍历的标识 i 且对EndIndex做自减操作。
这样就可以以列表的 [0, EndIndex ) 作为遍历结果。
不过面试官似乎对这种做法不太满意。因为这样会破坏原有列表的顺序,也不算真正的删除。
搜索了一下发现比较主流的做法是倒叙对List进行遍历(因为倒着遍历即使把元素删除了也不会影响接下来要遍历的元素),然后通过RemoveAt删除对应的元素。
不过在 C#的源码中(RemoveAt (microsoft.com))
可以看到每一次调用 RemoveAt 都会调用一次 Array.Copy 方法。
简单地测试后有以下结果 (不代表绝对时间,仅作为两者间耗时的比例参考)
在数组元素数量比较小的时候 Array.Copy 与 for循环的效率差别不大。且最佳情况时,Copy方法的效率是for的10倍左右。
而上面也提到了RemoveAt方法在每一次调用的时候,都会调用Copy方法,相当于做10%的for循环遍历。(比较好的情况下)
那也就是说, 在要被删除的数量大于10个的时候,使用 EndIndex标记循环会更加高效一些。
同时如果需要返回值的话,也可以使用List.GetRange 方法。
在实际测试之后,有以下结果(包含GetRange )
可以看到在绝大部分情况下,多次RemoveAt的效率都不如先交换后GetRange
所以在删除元素的时候,如果删除的元素数量比较多,且也不是那么在意元素循序,我们可以选择交换元素后GetRage的方法来提高效率。
备注:在随后的测试中发现,即使在意列表顺序,在GetRange后调用Sort也会比倒叙RemoveAt快。
List.GetRange的位置:GetRange (microsoft.com)