繁体   English   中英

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

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

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

  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块中的项目的原因是这会抛出异常(“集合已修改......”)

我想做以下事情:

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

但是 Dictionary<> 类没有像 List<> 类那样公开 RemoveAll(Predicate<> Match) 方法。

做到这一点的最佳方法是什么(无论是性能方面还是优雅方面)?

这是另一种方式

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

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

您可以创建一个扩展方法

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

而不是删除,只是做相反的事情。 从仅包含您感兴趣的元素的旧字典创建一个新字典。

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

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

最快的删除方法是:

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) ,但是如果“删除/查找”的情况非常少,第一种方法会更快,如果集合中的项目在大多数情况下都符合条件,则第二种方法会更快。

你能改变你的循环来使用一个索引(即FOR而不是FOREACH)吗? 当然,您必须向后循环,即 count-1 减至零。

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

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

从 .NET 3.0 开始,现在允许在枚举时从Dictionary<TKey,TValue>中删除项目。 根据文档

仅限 .NET Core 3.0+:唯一不会使枚举数无效的变异方法是RemoveClear

这是提出并批准此更改的 GitHub 问题: Allow Dictionary<K,V>.Remove during enumeration

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

使用示例:

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

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

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