Редактирование значений словаря в цикле foreach
Я пытаюсь построить круговую диаграмму из словаря. Прежде чем я покажу круговую диаграмму, я хочу привести в порядок данные. Я удаляю любые куски пирога, которые были бы менее 5% пирога и помещаю их в "другой" кусок пирога. Однако я получаю Collection was modified; enumeration operation may not execute исключение во время выполнения.
Я понимаю, почему вы не можете добавлять или удалять элементы из словаря, повторяя их. Однако я не понимаю, почему вы не можете просто изменить значение существующего ключа в командлет foreach петля.
любые предложения по исправлению моего кода будут оценены.
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);
12 ответов:
установка значения в словаре обновляет его внутренний "номер версии", что делает недействительным итератор и любой итератор, связанный с коллекцией ключей или значений.
я поняла твою точку зрения, но в то же время было бы странно, если набор значений может меняться в середине итерации и для простоты есть только один номер версии.
обычный способ исправления такого рода вещей заключается в том, чтобы либо скопировать коллекцию ключей заранее и повторить копию, или повторите исходную коллекцию, но сохраните коллекцию изменений, которые вы примените после завершения итерации.
например:
копирование ключей первого
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; } }или...
создание списка модификаций
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; }
вызов
ToList()наforeachпетли. Таким образом, нам не нужна временная переменная копия. Это зависит от Linq, который доступен с .Net 3.5.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 в любом случае.
Если вы редактируете a из значения, которое вы храните, это было бы нормально, но вы редактируете само значение, и IEnumberable это не нравится.
решение, которое я использовал, чтобы устранить цикл foreach и просто использовать цикл for. Простой цикл for не будет проверять наличие изменений, которые, как вы знаете, не повлияют на коллекцию.
вот как вы могли бы сделать это:
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, но вы можете изменить их члены. Например, это должно работать:
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 } );
Как насчет того, чтобы просто сделать некоторые запросы linq против вашего словаря, а затем привязать свой график к результатам этих?...
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); }
Если вы чувствуете себя творческим, вы могли бы сделать что-то подобное. Петли назад через словарь, чтобы внести изменения.
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)); ; } }конечно, не идентичны, но вы могли бы быть заинтересованы в любом случае...
вам нужно создать новый словарь из старого, а не изменять на месте. Что-то вроде (также перебирайте KeyValuePair вместо использования ключевого поиска:
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;
вы не можете изменить коллекцию, даже не ценности. Вы можете сохранить эти случаи и удалить их позже. Это закончилось бы так:
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);
отказ от ответственности: я не делаю много C#
вы пытаетесь изменить объект DictionaryEntry, который хранится в хэш-таблице. В хэш-таблице хранится только один объект-ваш экземпляр DictionaryEntry. Изменение ключа или значения достаточно, чтобы изменить хэш-таблицу и привести к тому, что перечислитель станет недействительным.
вы можете сделать это вне цикла:
if(hashtable.Contains(key)) { hashtable[key] = value; }сначала создав список всех ключей значений, которые вы хотите изменить и повторить вместо этого список.
вы можете сделать копию списка
dict.Values, вы можете использоватьList.ForEachлямбда-функцию для итерации (илиforeachцикл, как предлагалось ранее).new List<string>(myDict.Values).ForEach(str => { //Use str in any other way you need here. Console.WriteLine(str); });
начиная с .NET 4.5 вы можете сделать это с ConcurrentDictionary:
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);обратите внимание, однако, что его производительность на самом деле намного хуже, чем простой
foreach dictionary.Kes.ToArray():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>(); } }результат:
Method | Mean | Error | StdDev | --------------------- |----------:|----------:|----------:| ConcurrentDictionary | 182.24 us | 3.1507 us | 2.7930 us | Dictionary | 47.01 us | 0.4824 us | 0.4512 us |
наряду с другими ответами, я думал, что отмечу, что если вы получите
sortedDictionary.KeysилиsortedDictionary.Valuesа затем цикл над ними сforeach, вы также проходите в отсортированном порядке. Это потому, что эти методы возвращаютSystem.Collections.Generic.SortedDictionary<TKey,TValue>.KeyCollectionилиSortedDictionary<TKey,TValue>.ValueCollectionобъекты, которые поддерживают вид исходного словаря.
Comments