有点离题,但说我想从列表中删除所有 2。这是一种非常优雅的方法。
void RemoveAll<T>(T item,List<T> list)
{
while(list.Contains(item)) list.Remove(item);
}
带谓词:
void RemoveAll<T>(Func<T,bool> predicate,List<T> list)
{
while(list.Any(predicate)) list.Remove(list.First(predicate));
}
+1 只是为了鼓励您在此处留下您的答案以用于学习目的。你说的离题也是对的,但我不会因此而对你说,因为在这里留下你的例子有很大的价值,再次,严格用于学习目的。我将此回复作为编辑发布,因为将其发布为一系列 cmets 会不守规矩。
尽管您的示例简短而紧凑,但在效率方面都不是优雅的;第一个在 O(n2) 时很糟糕,第二个在 O(n3) 时绝对糟糕透顶。 O(n2) 的算法效率很差,应尽可能避免,尤其是在通用代码中; O(n3) 的效率是可怕的,在所有情况下都应该避免,除非你知道 n 总是非常小。有些人可能会抛出他们的“过早的优化是万恶之源”的战斗轴,但他们这样做很天真,因为他们没有真正理解二次增长的后果,因为他们从未编写过必须处理大型数据集的算法。结果,他们的小数据集处理算法通常运行得比他们可以运行的慢,而且他们不知道他们可以运行得更快。高效算法和低效算法之间的差异通常很细微,但性能差异可能很大。了解算法性能的关键是了解您选择使用的原语的性能特征。
在您的第一个示例中,list.Contains() 和 Remove() 都是 O(n),所以一个 while() 循环,其中一个在谓词中,另一个在主体中是 O(n2);好吧,从技术上讲 O(m*n),但它接近 O(n2),因为要删除的元素数量 (m) 接近列表的长度 (n)。
你的第二个例子更糟糕:O(n3),因为每次你调用Remove(),你也调用First(predicate),这也是O(n)。想一想:Any(predicate) 循环遍历列表 寻找predicate() 返回true 的任何元素。一旦找到第一个这样的元素,它就会返回 true。在while() 循环的主体中,然后调用list.First(predicate),它第二次循环遍历列表,寻找list.Any(predicate) 已经找到的相同元素。一旦First() 找到它,它就会返回传递给list.Remove() 的元素,该元素第三次循环列表 再次找到@987654335 之前找到的相同元素@ 和First(),以便最终将其删除。移除后,整个过程从头开始,列表稍短,所有循环一遍又一遍地从头开始直到最后没有更多匹配谓词的元素仍然存在。所以你的第二个例子的性能是 O(m*m*n),或者当 m 接近 n 时 O(n3)。
从列表中删除与某个谓词匹配的所有项目的最佳选择是使用通用列表自己的List<T>.RemoveAll(predicate) 方法,只要您的谓词为 O(1),该方法就是 O(n)。 for() 循环技术只通过列表一次,为每个要删除的元素调用 list.RemoveAt(),可能 似乎 是 O(n),因为它似乎只通过循环一次.这样的解决方案 比您的第一个示例更有效,但只是通过一个常数因子,就算法效率而言可以忽略不计。即使是 for() 循环实现也是 O(m*n),因为对 Remove() 的每次调用都是 O(n)。由于 for() 循环本身是 O(n),并且它调用了 Remove() m 次,所以当 m 接近 n 时,for() 循环的增长是 O(n2)。