Вопрос по c#, .net – Редактирование значений словаря в цикле 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);
Позвони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;
}
}
foreach(var pair in colStates.ToList())
чтобы избежать доступа к ключуа такж Значение, которое избегает необходимости звонить вcolStates[key]
..
user2864740
который делает недействительным итератор и любой итератор, связанный с коллекцией ключей или значений.
Я понимаю вашу точку зрения, но в то же время было бы странно, если бы коллекция значений могла измениться в середине итерации - и для простоты есть только один номер версии.
Обычный способ исправления такого рода вещей - это либо заранее скопировать коллекцию ключей и выполнить итерацию по копии, либо выполнить итерацию по исходной коллекции, но сохранить коллекцию изменений, которые вы примените после завершения итерации.
Например
Сначала копируем ключи
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;
}
но вы можете изменять их элементы. Например, это должно работать:
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);
}
colStates [key] = 0;
Делая это, вы, по сути, удаляете и заново вставляете что-то в этот момент (что касается IEnumerable в любом случае.
Если вы редактируете Член значения, которое вы храните, это было бы нормально, но вы редактируете само значение, а 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;
}
}
ты можешь сделать что-то подобное. Перейдите назад по словарю, чтобы внести изменения.
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);
Вы можете сделать копию спискаdict.Values
, тогда вы можете использоватьList.ForEach
лямбда-функция для итерации, (илиforeach
цикл, как и предлагалось ранее).
new List<string>(myDict.Values).ForEach(str =>
{
//Use str in any other way you need here.
Console.WriteLine(str);
});
foreach
для побочного эффекта, создающего код.
user2864740
Вы пытаетесь изменить объект DictionaryEntry, который хранится в HashTable. Hashtable хранит только один объект - ваш экземпляр DictionaryEntry. Изменение ключа или значения достаточно, чтобы изменить HashTable и сделать перечислитель недействительным.
Вы можете сделать это вне цикла:
if(hashtable.Contains(key))
{
hashtable[key] = value;
}
сначала создав список всех ключей значений, которые вы хотите изменить, и переберите этот списо
Начиная с .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
объекты, которые поддерживают вид оригинального словаря.