从 .NET 字典中删除与谓词匹配的多个项目的最佳方法?

Posted

技术标签:

【中文标题】从 .NET 字典中删除与谓词匹配的多个项目的最佳方法?【英文标题】:Best way to remove multiple items matching a predicate from a .NET Dictionary? 【发布时间】:2010-10-02 22:08:58 【问题描述】:

我需要从字典中删除多个项目。 一个简单的方法如下:

  List<string> keystoremove= new List<string>();
  foreach (KeyValuePair<string,object> k in MyCollection)
     if (k.Value.Member==foo)
        keystoremove.Add(k.Key);
  foreach (string s in keystoremove)
        MyCollection.Remove(s);

我不能直接删除foreach块中的项目的原因是这样会抛出异常(“Collection was modified...”)

我想做以下事情:

 MyCollection.RemoveAll(x =>x.Member==foo)

但是 Dictionary 类不像 List 类那样公开 RemoveAll(Predicate Match) 方法。

最好的方法是什么(性能方面和优雅方面)?

【问题讨论】:

【参考方案1】:

这是另一种方式

foreach ( var s in MyCollection.Where(kv => kv.Value.Member == foo).ToList() ) 
  MyCollection.Remove(s.Key);

直接将代码推入列表可以避免“枚举时删除”问题。 .ToList() 将在 foreach 真正开始之前强制枚举。

【讨论】:

很好的答案,但我不认为 ToList() 是必需的。是吗? 好答案,但如果我没记错的话,s 是 Value 类型的一个实例,所以结尾的 s.key 不会编译,还是会编译? 我认为我不太喜欢这个解决方案,因为“.ToList()”。它的存在是有目的的,但在您删除 .ToList() 并自己观察错误之前,它的目的是不言而喻的。当有更多可读的替代方案可用时,我不会推荐此代码。 @Jim,.ToList 在这里是绝对必要的。 foreach 主体修改底层集合。如果没有 .ToList(),Where 子句将针对修改后的集合进行操作。使用 .ToList() 强制查询在任何删除发生之前完成 ToArray( ) 会比 ToList( ) 轻吗?【参考方案2】:

你可以创建一个extension method:

public static class DictionaryExtensions

    public static void RemoveAll<TKey, TValue>(this IDictionary<TKey, TValue> dict, 
        Func<TValue, bool> predicate)
    
        var keys = dict.Keys.Where(k => predicate(dict[k])).ToList();
        foreach (var key in keys)
        
            dict.Remove(key);
        
    


...

dictionary.RemoveAll(x => x.Member == foo);

【讨论】:

工作愉快。谢谢! 如果你想要predicate的值,那你为什么要枚举键,而不是直接枚举dict以获得键值对?通过为每个键查询字典,您会无缘无故地失去一些效率。【参考方案3】:

不要删除,而是执行相反的操作。从旧字典创建一个新字典,只包含您感兴趣的元素。

public Dictionary<T, U> NewDictionaryFiltered<T, U>
(
  Dictionary<T, U> source,
  Func<T, U, bool> filter
)

return source
  .Where(x => filter(x.Key, x.Value))
  .ToDictionary(x => x.Key, x => x.Value);

【讨论】:

过滤器从何而来?你能解释一下它是什么吗? 您可能需要添加 using System.Linq; 才能完成这项工作。【参考方案4】:

Aku 的扩展方法解决方案的修改版本。主要区别在于它允许谓词使用字典键。一个小的区别是它扩展了 IDictionary 而不是 Dictionary。

public static class DictionaryExtensions

    public static void RemoveAll<TKey, TValue>(this IDictionary<TKey, TValue> dic,
        Func<TKey, TValue, bool> predicate)
    
        var keys = dic.Keys.Where(k => predicate(k, dic[k])).ToList();
        foreach (var key in keys)
        
            dic.Remove(key);
        
    


. . .

dictionary.RemoveAll((k,v) => v.Member == foo);

【讨论】:

我回滚了社区所做的更改,因为它不包括 ToList() 导致“枚举时删除”问题。 感谢杰罗姆的灵感。除了这个和@Aku 的回答之外,我还为Func&lt;TKey, bool&gt;Func&lt;KeyValuePair&lt;TKey, TValue&gt;&gt; 创建了扩展重载,它们都可以一起工作(除非TKeyTValue 在编译器显然无法选择时属于同一类型在Func&lt;TKey, bool&gt;Func&lt;TValue, bool&gt; 之间)。如果有兴趣的人不知道如何实现它们,请在此处联系我,我会发布它们。 :-)【参考方案5】:

最快的删除方法是:

public static void RemoveAll<TKey, TValue>(this IDictionary<TKey, TValue> idict, Func<KeyValuePair<TKey, TValue>, bool> predicate)
    
        foreach (var kvp in idict.Where(predicate).ToList())
        
            idict.Remove(kvp.Key);
        
    

public static void RemoveAll<T>(this ICollection<T> icollection, Predicate<T> predicate)

    var nonMatchingItems = new List<T>();

    // Move all the items that do not match to another collection.
    foreach (var item in icollection) 
    
        if (!predicate(item))
        
            nonMatchingItems.Add(item);
        
    

    // Clear the collection and then copy back the non-matched items.
    icollection.Clear();
    foreach (var item in nonMatchingItems)
    
        icollection.Add(item);
    

取决于您是否有更多的谓词返回 true 的情况。两者本质上都是 O(N),但如果“删除/查找”的情况非常少,第一种方法会更快,如果集合中的项目在大多数情况下都符合条件,则第二种方法会更快。

【讨论】:

【参考方案6】:

你能改变你的循环以使用索引(即 FOR 而不是 FOREACH)吗?当然,您必须向后循环,即从 1 倒数到零。

【讨论】:

你不能用 FOR 遍历字典。 抱歉,我认为扩展名 .ElementAt(index) 至少在 .net 3.5 中会允许这样做。 @brann 哦,你可以使用 linq。 for (int index = 0; index @Geoff:假设字典包含三个键:“Moe”、“Larry”和“Curly”。 ;想删除所有不以“C”开头的键。第一次调用ElementAt(2) 将枚举所有三个项目并返回不应被删除的Curly。然后ElementAt(1) 将枚举两个项目,并返回拉里。删除 Larry 可能会任意重新排序项目,因此ElementAt(0) 可能会返回 Moe 或 Curly。如果它恰好返回 Curly,那么 Moe 将不会最终得到处理。 ElementAt 可能是合法的,但这并不意味着它会有效。【参考方案7】:

不要只删除,而是执行相反的操作(从仅包含您感兴趣的元素的旧字典创建一个新字典)并让垃圾收集器处理旧字典:

var newDictionary = oldDictionary.Where(x => x.Value != foo);

【讨论】:

这可能会导致糟糕的性能,不是吗? 有了 var 关键字,这不会给 newDictionary 一个 IEnumerable> 类型,而不是 Dictionary 吗? Enumerable.Select 不做过滤。【参考方案8】:

使用 LINQ 很简单。只需执行以下操作:)

MyCollection = MyCollection.Where(mc => !keystoremove.Contains(mc.Key))
.ToDictionary(d => d.Key, d => d.Value);

【讨论】:

以上是关于从 .NET 字典中删除与谓词匹配的多个项目的最佳方法?的主要内容,如果未能解决你的问题,请参考以下文章

在 Scala 中查找与谓词匹配的项目

从位于字典中的数组中删除与搜索词匹配的所有值(字符串)?

从用户的角度使用 predicateEditor 定义子谓词的最佳方法

如何使用 System.Linq.Dynamic 检查 JSON 对象是不是与谓词匹配

用于以任何出现顺序匹配具有多个单词的列中的字符串的 Coredata 谓词

通过删除字母匹配到字典里最长单词