简体   繁体   English

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

[英]Best way to remove multiple items matching a predicate from a .NET Dictionary?

I need to remove multiple items from a Dictionary.我需要从字典中删除多个项目。 A simple way to do that is as follows :一个简单的方法如下:

  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);

The reason why I can't directly Remove the items in the foreach block is that this would throw an Exception ("Collection was modified...")我不能直接删除foreach块中的项目的原因是这会抛出异常(“集合已修改......”)

I'd like to do the following :我想做以下事情:

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

But the Dictionary<> class doesn't expose a RemoveAll(Predicate<> Match) method, like the List<> Class does.但是 Dictionary<> 类没有像 List<> 类那样公开 RemoveAll(Predicate<> Match) 方法。

What's the best way (both performance wise and elegant wise) to do that?做到这一点的最佳方法是什么(无论是性能方面还是优雅方面)?

Here's an alternate way这是另一种方式

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

Pushing the code into a list directly allows you to avoid the "removing while enumerating" problem.将代码直接推入列表可以避免“枚举时删除”问题。 The .ToList() will force the enumeration before the foreach really starts. .ToList()将在 foreach 真正开始之前强制枚举。

you can create an 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);

Instead of removing, just do the inverse.而不是删除,只是做相反的事情。 Create a new dictionary from the old one containing only the elements you are interested in.从仅包含您感兴趣的元素的旧字典创建一个新字典。

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);
}

Modified version of Aku's extension method solution. Aku 的扩展方法解决方案的修改版本。 Main difference is that it allows the predicate to use the dictionary key.主要区别在于它允许谓词使用字典键。 A minor difference is that it extends IDictionary rather than Dictionary.一个小的区别是它扩展了 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);

The fastest way to remove would be either:最快的删除方法是:

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);
        }
    }

or或者

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);
    }
}

depending on whether you have more cases of predicate returning true or not.取决于您是否有更多的谓词返回 true 的情况。 Both are O(N) in nature, but 1st approach will be faster if you have very less cases of "removal/lookup", and the second one faster if items in collection matches the condition majority of the times.两者本质上都是 O(N) ,但是如果“删除/查找”的情况非常少,第一种方法会更快,如果集合中的项目在大多数情况下都符合条件,则第二种方法会更快。

Can you just change your loop to use an index (ie FOR instead of FOREACH)?你能改变你的循环来使用一个索引(即FOR而不是FOREACH)吗? You'd have to loop backwards, of course, ie count-1 down to zero.当然,您必须向后循环,即 count-1 减至零。

而不是删除只是做相反的事情(从旧字典创建一个只包含您感兴趣的元素的新字典)并让垃圾收集器处理旧字典:

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

Starting from the .NET 3.0, it's now allowed to remove items from a Dictionary<TKey,TValue> while enumerating it.从 .NET 3.0 开始,现在允许在枚举时从Dictionary<TKey,TValue>中删除项目。 According to the documentation :根据文档

.NET Core 3.0+ only: The only mutating methods which do not invalidate enumerators are Remove and Clear .仅限 .NET Core 3.0+:唯一不会使枚举数无效的变异方法是RemoveClear

Here is the GitHub issue where this change was proposed and approved: Allow Dictionary<K,V>.Remove during enumeration这是提出并批准此更改的 GitHub 问题: Allow Dictionary<K,V>.Remove during enumeration

So the RemoveAll extension method can be implemented simply like this:所以RemoveAll扩展方法可以像这样简单地实现:

/// <remarks>.NET Core 3.0+ only.</remarks>
public static void RemoveAll<TKey, TValue>(this Dictionary<TKey, TValue> source,
    Predicate<KeyValuePair<TKey, TValue>> predicate)
{
    foreach (var pair in source)
        if (predicate(pair))
            source.Remove(pair.Key);
}

Usage example:使用示例:

myDictionary.RemoveAll(e => e.Value.Member == foo);

It's simple using LINQ.使用 LINQ 很简单。 Just do the following :)只需执行以下操作:)

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM