【问题标题】:Best way to remove multiple items matching a predicate from a .NET Dictionary?从 .NET 字典中删除与谓词匹配的多个项目的最佳方法?
【发布时间】: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) 方法。

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

【问题讨论】:

    标签: c# .net linq collections dictionary


    【解决方案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);
      

      【讨论】:

        猜你喜欢
        • 2011-08-08
        • 2022-08-24
        • 2017-09-13
        • 1970-01-01
        • 1970-01-01
        • 2014-06-22
        • 2012-03-22
        • 1970-01-01
        • 2010-09-17
        相关资源
        最近更新 更多