【问题标题】:What is the disadvantages of using List<T>.RemoveAll like ForEach?像 ForEach 一样使用 List<T>.RemoveAll 有什么缺点?
【发布时间】:2016-11-21 15:38:17
【问题描述】:

在某些代码中,我使用 List&lt;T&gt;.RemoveAll 作为特殊的 List&lt;T&gt;.ForEach,它允许动态删除元素,因为我认为它会提供更好的性能:O(n) 与一个组合循环在 RemoveAllO(2n) 中,一个 ForEach 和一个 RemoveAll

例子:

gameObjects.RemoveAll( object => {
    if (object.Active){
        // Doing stuffs on object
    }
    return !object.Active;
});

至少在我的代码中,这个 hack 工作正常,因为它以正确的顺序迭代并且到目前为止没有遇到任何错误。

这种 hack 的一个缺点是代码的可维护性/可读性,因为它不是这种方法的原始目的,以后可能会引起混乱。

所以我的问题是:这种 hack 是否还有其他缺点(性能、可能的错误……)?

【问题讨论】:

  • 您在此处需要更好的性能吗?您是否在代码中发现了证明“黑客”合理的性能问题?
  • 你写的O(2n)是什么意思?
  • 这不是你说的“黑客”吗?也以这种方式看到并使用它与其他东西
  • 如果您依赖保留顺序,请不要使用它。没有这样的保证。除此之外,这是风格问题——有人可能不在乎,有人可能想把你的四肢和身体分开。忘记算法的复杂性——你也不能保证,因为RemoveAll方法(或ForEach)的实现不是合同的一部分。无论如何,在所有更新之后推迟删除可能会更好地为您服务 - 否则当对象依赖于其他对象并且您以未指定的顺序删除它们时,您会获得很多乐趣。
  • @Tr1et 但是 O(2n) == O(n)。

标签: c# list foreach


【解决方案1】:

在一次迭代中做两倍的工作实际上并不比两次迭代的效率高一倍,每次都做一半的工作。

跑一圈一英里长的赛道,还是跑两圈半英里长的赛道,哪个更快?

尝试在一次迭代中完成所有这些操作会使代码变得不必要地复杂化,而且实际上并没有从中受益。只需调用RemoveAll 即可删除所有非活动项目,然后foreach 在集合上处理活动项目。

【讨论】:

  • 除非迭代代价高昂(例如创建枚举器会发生长时间运行的事情),否则像这样的 技巧 可能会做到。 List.RemoveAll() 不是这种情况。
  • 谢谢,所以可以将 log 或 count 之类的琐碎内容放在 remove all 中,对吧?
  • @Tr1et 这一切都归结为该操作在概念上是否是删除操作的一部分。做一些像计算被删除的项目数量这样的事情似乎对我来说是合格的。您要避免的是与被放入其中的移除无关的操作,例如对所有活动项目执行操作。
【解决方案2】:

我并不是认为这是一种 hack,而是以特定方式使用设计元素。因为它似乎按照文档完成了它的工作删除所有与指定谓词定义的条件匹配的元素。 (List.RemoveAll )

...工作正常,因为它以正确的顺序迭代并且没有遇到 到目前为止的错误。

这里有两个步骤,一个是对每个项目进行操作,第二个是从列表中删除。使用此方法有效且可读。已经预先确定删除的确定这一事实是无关紧要的。

纯粹主义者会抱怨,但操作无论如何都需要循环遍历项目,为什么不集中工作

代码可维护性/可读性

为了维护......只需将意图评论给任何未来的开发者。

这种 hack 是否还有其他缺点(性能、可能的错误……)?

不,当一个 逻辑上 可以完成时,我会将 hack 视为具有两个循环。

【讨论】:

  • 您似乎假设正在完成的工作是计算是否要删除该项目。它不是。业务逻辑是删除所有非活动项并对所有活动项执行一些工作。它们在概念上是独立的操作; OP 询问将这些单独的业务任务组合成一个方法调用以提高性能的优点。如果正在执行的操作是确定是否应该删除该项目,那将是完全不同的事情。
  • 您觉得这种转换非常令人困惑,以至于您觉得需要评论才能向未来的读者解释它的行为,这一事实非常强烈地表明它是比分离单独的操作更具可读性/可维护性。如果您觉得将它们组合在一起更具可读性,那么为什么值得这样解释?
【解决方案3】:

我在 Stackoverflow 上找到了这个: Stackoverflow - Maybe same 我想你的方法行得通,我认为没有问题,但正如你所说,有一天可能很难理解。我更喜欢“后循环”

这是上面 链接 中描述的另一种方式:正如智者所评论的那样,速度较慢 O(n*m) - 最坏的情况

var list = new List<int>(Enumerable.Range(1, 10));
for (int i = list.Count - 1; i >= 0; i--)
{
    if (list[i] > 5)
    list.RemoveAt(i);
 }
list.ForEach(i => Console.WriteLine(i));

【讨论】:

  • 通过调用RemoveAtm从列表中删除m项目是O(n * m),而调用RemoveAll是O(n),因为它只需要这样做列表的一个碎片整理。
  • 您是在建议让 OP 的代码变得更糟,无论是在可读性方面,还是在性能方面都非常显着,并在您因提供有害内容而遭到否决时抱怨?你认为我应该支持一个暗示有害变化的帖子,即使我知道得更好?我不是那么刻薄。其他人发布了对问题的糟糕解决方案并不意味着它不是对问题的糟糕解决方案(尽管还要注意该答案确实提到了RemoveAll)。最后,虽然您只显式迭代一次,RemoveAt 必须在内部迭代集合。
  • ...您确实对任何不合您意见的东西投了反对票。问题是是否有另一种更具可读性的方式 - 如果是这样,这就是它的答案,还有 400 多人在上面发布的帖子中认为。
  • 我什至在上面说过,这是同一个问题——它或多或少是一个重复...
  • 大家好。这次谈话毫无意义。让我们继续前进吧。
猜你喜欢
  • 1970-01-01
  • 2010-10-19
  • 2011-01-15
  • 1970-01-01
  • 2010-12-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多