从 .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<TKey, bool>
和Func<KeyValuePair<TKey, TValue>>
创建了扩展重载,它们都可以一起工作(除非TKey
和TValue
在编译器显然无法选择时属于同一类型在Func<TKey, bool>
或Func<TValue, bool>
之间)。如果有兴趣的人不知道如何实现它们,请在此处联系我,我会发布它们。 :-)【参考方案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使用 LINQ 很简单。只需执行以下操作:)
MyCollection = MyCollection.Where(mc => !keystoremove.Contains(mc.Key))
.ToDictionary(d => d.Key, d => d.Value);
【讨论】:
以上是关于从 .NET 字典中删除与谓词匹配的多个项目的最佳方法?的主要内容,如果未能解决你的问题,请参考以下文章
从用户的角度使用 predicateEditor 定义子谓词的最佳方法
如何使用 System.Linq.Dynamic 检查 JSON 对象是不是与谓词匹配