Editing dictionary values in a foreach loop
我试着从词典中构建一个喜鹊图。在我展示喜鹊图之前,我想把数据输入。我把每一块碎屑都清理干净了,它们的碎屑比碎屑的5%还要少,并将它们放在一个"其他"的碎屑中。但我还是得到了一个
我明白你为什么不能从词典中添加或删除项目,而在词典中重复这些项目。但我不明白为什么你不能简单地改变一个在前轮中存在的关键。
有什么建议:确定我的代码,值得赞赏。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | Dictionary<string, int> colStates = new Dictionary<string,int>(); // ... // Some code to populate colStates dictionary // ... int OtherCount = 0; foreach(string key in colStates.Keys) { double Percent = colStates[key] / TotalCount; if (Percent < 0.05) { OtherCount += colStates[key]; colStates[key] = 0; } } colStates.Add("Other", OtherCount); |
在字典中设置值将更新其内部"版本号"——这将使迭代器和与键或值集合关联的任何迭代器失效。
我确实看到了您的观点,但同时,如果值集合可以在迭代过程中更改,那将是很奇怪的——为了简单起见,只有一个版本号。
修复这类问题的正常方法是预先复制键集合并在副本上迭代,或者迭代原始集合,但维护一组更改,这些更改将在迭代完成后应用。
例如:
先复制密钥
1 2 3 4 5 6 7 8 9 10 | List<string> keys = new List<string>(colStates.Keys); foreach(string key in keys) { double percent = colStates[key] / TotalCount; if (percent < 0.05) { OtherCount += colStates[key]; colStates[key] = 0; } } |
或者…
创建修改列表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | List<string> keysToNuke = new List<string>(); foreach(string key in colStates.Keys) { double percent = colStates[key] / TotalCount; if (percent < 0.05) { OtherCount += colStates[key]; keysToNuke.Add(key); } } foreach (string key in keysToNuke) { colStates[key] = 0; } |
调用
1 2 3 4 5 6 7 8 9 10 11 12 | using System.Linq; foreach(string key in colStates.Keys.ToList()) { double Percent = colStates[key] / TotalCount; if (Percent < 0.05) { OtherCount += colStates[key]; colStates[key] = 0; } } |
您正在修改此行中的集合:
colStates[key] = 0;
通过这样做,您基本上就是在那一点上删除和重新插入某些内容(就IEnumerable而言,无论如何都是这样)。
如果编辑要存储的值的成员,这没关系,但您正在编辑值本身,IEnumerable不喜欢这样。
我使用的解决方案是消除foreach循环,只使用for循环。简单的for循环不会检查您知道不会影响集合的更改。
你可以这样做:
1 2 3 4 5 6 7 8 9 10 11 | List<string> keys = new List<string>(colStates.Keys); for(int i = 0; i < keys.Count; i++) { string key = keys[i]; double Percent = colStates[key] / TotalCount; if (Percent < 0.05) { OtherCount += colStates[key]; colStates[key] = 0; } } |
不能直接在foreach中修改键或值,但可以修改它们的成员。例如,这应该有效:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | public class State { public int Value; } ... Dictionary<string, State> colStates = new Dictionary<string,State>(); int OtherCount = 0; foreach(string key in colStates.Keys) { double Percent = colStates[key].Value / TotalCount; if (Percent < 0.05) { OtherCount += colStates[key].Value; colStates[key].Value = 0; } } colStates.Add("Other", new State { Value = OtherCount } ); |
如果你觉得有创造力,你可以这样做。向后循环浏览字典以进行更改。
1 2 3 4 5 6 7 8 9 10 11 12 13 | Dictionary<string, int> collection = new Dictionary<string, int>(); collection.Add("value1", 9); collection.Add("value2", 7); collection.Add("value3", 5); collection.Add("value4", 3); collection.Add("value5", 1); for (int i = collection.Keys.Count; i-- > 0; ) { if (collection.Values.ElementAt(i) < 5) { collection.Remove(collection.Keys.ElementAt(i)); ; } } |
当然不完全相同,但你可能对…
只需对字典执行一些LINQ查询,然后将图形绑定到这些查询的结果中,如何?…
1 2 3 4 5 6 7 8 | var under = colStates.Where(c => (decimal)c.Value / (decimal)totalCount < .05M); var over = colStates.Where(c => (decimal)c.Value / (decimal)totalCount >= .05M); var newColStates = over.Union(new Dictionary<string, int>() { {"Other", under.Sum(c => c.Value) } }); foreach (var item in newColStates) { Console.WriteLine("{0}:{1}", item.Key, item.Value); } |
您需要从旧词典创建一个新词典,而不是就地修改。一些类似的(也迭代keyValuePair<,>而不是使用键查找:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | int otherCount = 0; int totalCounts = colStates.Values.Sum(); var newDict = new Dictionary<string,int>(); foreach (var kv in colStates) { if (kv.Value/(double)totalCounts < 0.05) { otherCount += kv.Value; } else { newDict.Add(kv.Key, kv.Value); } } if (otherCount > 0) { newDict.Add("Other", otherCount); } colStates = newDict; |
您不能修改集合,甚至不能修改值。您可以保存这些案例,稍后再将其删除。结果会是这样:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | Dictionary<string, int> colStates = new Dictionary<string, int>(); // ... // Some code to populate colStates dictionary // ... int OtherCount = 0; List<string> notRelevantKeys = new List<string>(); foreach (string key in colStates.Keys) { double Percent = colStates[key] / colStates.Count; if (Percent < 0.05) { OtherCount += colStates[key]; notRelevantKeys.Add(key); } } foreach (string key in notRelevantKeys) { colStates[key] = 0; } colStates.Add("Other", OtherCount); |
除了其他答案,我想我会注意到,如果你得到
从.NET 4.5开始,您可以使用ConcurrentDictionary执行此操作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | using System.Collections.Concurrent; var colStates = new ConcurrentDictionary<string,int>(); colStates["foo"] = 1; colStates["bar"] = 2; colStates["baz"] = 3; int OtherCount = 0; int TotalCount = 100; foreach(string key in colStates.Keys) { double Percent = (double)colStates[key] / TotalCount; if (Percent < 0.05) { OtherCount += colStates[key]; colStates[key] = 0; } } colStates.TryAdd("Other", OtherCount); |
但是要注意,它的性能实际上比简单的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Running; public class ConcurrentVsRegularDictionary { private readonly Random _rand; private const int Count = 1_000; public ConcurrentVsRegularDictionary() { _rand = new Random(); } [Benchmark] public void ConcurrentDictionary() { var dict = new ConcurrentDictionary<int, int>(); Populate(dict); foreach (var key in dict.Keys) { dict[key] = _rand.Next(); } } [Benchmark] public void Dictionary() { var dict = new Dictionary<int, int>(); Populate(dict); foreach (var key in dict.Keys.ToArray()) { dict[key] = _rand.Next(); } } private void Populate(IDictionary<int, int> dictionary) { for (int i = 0; i < Count; i++) { dictionary[i] = 0; } } } public class Program { public static void Main(string[] args) { BenchmarkRunner.Run<ConcurrentVsRegularDictionary>(); } } |
结果:
1 2 3 4 | Method | Mean | Error | StdDev | --------------------- |----------:|----------:|----------:| ConcurrentDictionary | 182.24 us | 3.1507 us | 2.7930 us | Dictionary | 47.01 us | 0.4824 us | 0.4512 us | |
您可以制作
1 2 3 4 5 | new List<string>(myDict.Values).ForEach(str => { //Use str in any other way you need here. Console.WriteLine(str); }); |
免责声明:我不做太多C#
您正在尝试修改存储在哈希表中的DictionaryEntry对象。哈希表只存储一个对象——DictionaryEntry的实例。更改键或值足以更改哈希表并导致枚举器无效。
您可以在循环之外执行此操作:
1 2 3 4 | if(hashtable.Contains(key)) { hashtable[key] = value; } |
首先创建一个列表,列出您希望更改的值的所有键,然后迭代该列表。