List<object>.RemoveAll - 如何创建适当的谓词

Posted

技术标签:

【中文标题】List<object>.RemoveAll - 如何创建适当的谓词【英文标题】:List<object>.RemoveAll - How to create an appropriate Predicate 【发布时间】:2011-03-05 09:48:26 【问题描述】:

这是一个菜鸟问题 - 我对 C# 和泛型还很陌生,对谓词、委托和 lambda 表达式完全陌生...

我有一个“查询”类,其中包含另一个名为“车辆”的类的通用列表。我正在构建代码以从父查询中添加/编辑/删除车辆。目前,我正在专门研究删除。

从我目前阅读的内容来看,我似乎可以使用 Vehicles.RemoveAll() 来删除具有特定 VehicleID 的项目或具有特定 EnquiryID 的所有项目。我的问题是理解如何提供 .RemoveAll 正确的谓词 - 我看到的示例过于简单(或者由于我缺乏谓词、委托和 lambda 表达式的知识,我可能过于简单了)。

如果我有一个List&lt;Of Vehicle&gt; Vehicles,其中每辆车都有一个EnquiryID,我将如何使用Vehicles.RemoveAll() 删除给定查询ID 的所有车辆?

我知道有几种方法可以解决这个问题,所以我很想听听方法之间的区别 - 尽管我需要让某些东西发挥作用,但这也是一种学习练习。

作为补充问题,通用列表是这些对象的最佳存储库吗?我的第一个倾向是收藏,但似乎我已经过时了。当然泛型似乎是首选,但我对其他替代方案很好奇。

【问题讨论】:

3 个快速、准确的答案,均附有示例和说明。所有人都应该被“接受”,但由于我不能接受,Angelodev 获得了额外的积分来启动他的 SO 帐户。 【参考方案1】:

RemoveAll() 方法接受一个Predicate&lt;T&gt; 委托(直到这里没有新的东西)。谓词指向一个简单地返回真或假的方法。当然,RemoveAll 将从集合中删除所有返回 True 且应用了谓词的 T 实例。

C# 3.0 允许开发人员使用多种方法将谓词传递给RemoveAll 方法(不仅是这个……)。你可以使用:

Lambda 表达式

vehicles.RemoveAll(vehicle => vehicle.EnquiryID == 123);

匿名方法

vehicles.RemoveAll(delegate(Vehicle v) 
  return v.EnquiryID == 123;
);

普通方法

vehicles.RemoveAll(VehicleCustomPredicate);
private static bool
VehicleCustomPredicate (Vehicle v) 
    return v.EnquiryID == 123; 

【讨论】:

【参考方案2】:

T 中的谓词是一个接受 T 并返回布尔值的委托。 List.RemoveAll 将删除列表中调用谓词返回 true 的所有元素。提供简单谓词的最简单方法通常是lambda expression,但您也可以使用anonymous methods 或实际方法。


    List<Vehicle> vehicles;
    // Using a lambda
    vehicles.RemoveAll(vehicle => vehicle.EnquiryID == 123);
    // Using an equivalent anonymous method
    vehicles.RemoveAll(delegate(Vehicle vehicle)
    
        return vehicle.EnquiryID == 123;
    );
    // Using an equivalent actual method
    vehicles.RemoveAll(VehiclePredicate);


private static bool VehiclePredicate(Vehicle vehicle)

    return vehicle.EnquiryID == 123;

【讨论】:

【参考方案3】:

这应该可以工作(enquiryId 是您需要匹配的 id):

vehicles.RemoveAll(vehicle => vehicle.EnquiryID == enquiryId);

它的作用是将列表中的每辆车传递到 lambda 谓词中,并评估该谓词。如果谓词返回真(即vehicle.EnquiryID == enquiryId),则当前车辆将从列表中删除。

如果您知道集合中对象的类型,那么使用泛型集合是更好的方法。从集合中检索对象时,它可以避免强制转换,但如果集合中的项目是值类型(这可能会导致性能问题),也可以避免装箱。

【讨论】:

【参考方案4】:

有点离题,但说我想从列表中删除所有 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&lt;T&gt;.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)。

【讨论】:

2015 年 4 月 2 日,即我 46 岁生日的前一天,添加了关于算法效率的内联 cmets。 内联 cmets 是在答案后 1 年添加的?哇...确实确实花了编辑一些时间来编写这些 cmets :D【参考方案5】:

我想解决迄今为止没有答案的问题:

从我目前阅读的内容来看,我似乎可以使用 Vehicles.RemoveAll() 删除具有特定 VehicleID 的项目。 作为一个[原文如此] 补充问题,通用列表是这些对象的最佳存储库吗?

假设VehicleID 顾名思义是唯一的,当您拥有大量车辆时,列表是一种非常低效的存储方式,因为移除(和其他方法,如Find)仍然是 O(n)。看看HashSet&lt;Vehicle&gt;,它有 O(1) 删除(和其他方法),使用:

int GetHashCode(Vehicle vehicle)return vehicle.VehicleID;
int Equals(Vehicle v1, Vehicle v2)return v1.VehicleID == v2.VehicleID;

删除具有特定 EnquiryID 的所有车辆仍需要以这种方式迭代所有元素,因此您可以考虑使用 GetHashCode 来代替返回 EnquiryID,具体取决于您更频繁地执行哪种操作。但是,如果很多车辆共享相同的 EnquiryID,这会有很多碰撞的缺点。

在这种情况下,更好的选择是创建一个 Dictionary&lt;int, List&lt;Vehicle&gt;&gt;,将 EnquiryID 映射到 Vehicles 并在添加/删除车辆时保持最新。然后从 HashSet 中删除这些车辆是一个 O(m) 操作,其中 m 是具有特定 EnquiryID 的车辆的数量。

【讨论】:

这里没有 necro 这样的东西。新的(有用的)信息总是受欢迎的,即使在事实发生几年后,大多数问题仍然具有相关性。

以上是关于List<object>.RemoveAll - 如何创建适当的谓词的主要内容,如果未能解决你的问题,请参考以下文章

Java List --remove(int index)与remove(Object o)方法的区别

用List来实现一个简单的Map(包含key, 和Value),这个简单Map需要提供(add, get, remove)的基本功能。

关于C++ std list remove()的问题

java Stack pop返回值作为list.remove参数遇到问题

List 集合remove问题

java 集合 Arraylist<Character> list怎么移除字符? list.remove('的')不管用