Coder Perfect

In a foreach loop, changing dictionary values

Problem

I’m attempting to make a pie chart out of a dictionary. I’d like to clean up the data before displaying the pie chart. I’m going to put any pie slices that would make up less than 5% of the pie in a “Other” pie slice. However, at runtime, I receive a Collection was updated; enumeration operation may not perform exception.

I see why you can’t add or remove elements from a dictionary while iterating through it. However, I’m not sure why you can’t just modify the value of an existing key inside the foreach loop.

Any suggestions re: fixing my code, would be appreciated.

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

Asked by Aheho

Solution #1

Setting a value in a dictionary changes its internal “version number,” invalidating the iterator as well as any other iterators linked to the keys or values collection.

I get your reasoning, however it would be strange if the values collection could change in the middle of an iteration – especially because there is only one version number.

The standard way to fix this is to either clone the collection of keys beforehand and iterate over the copy, or loop over the original collection while keeping a list of modifications to apply when you’ve finished iterating.

For example:

Copying keys first

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

Or…

Creating a modification list

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

Answered by Jon Skeet

Solution #2

In the foreach loop, call ToList(). This eliminates the requirement for a transient variable copy. Linq, which has been accessible since, is used. 3.5 on the net.

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

Answered by DIG

Solution #3

You are modifying the collection in this line:

You’re essentially deleting and reinserting stuff at that point (at least as far as IEnumerable is concerned).

It’s fine if you update a member of the value you’re saving, but you’re editing the value itself, which IEnumberable doesn’t like.

The method I used was to remove the foreach loop and replace it with a for loop. A basic for loop will ignore changes that aren’t likely to affect the collection.

Here’s how you could go about doing it:

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

Answered by CodeFusionMobile

Solution #4

You can’t modify the keys nor the values directly in a ForEach, but you can modify their members. For example, this should work:

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

Answered by Jeremy Frey

Solution #5

Why not simply run some Linq queries against your dictionary and then bind your graph to the results? …

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

Answered by Scott Ivey

Post is based on https://stackoverflow.com/questions/1070766/editing-dictionary-values-in-a-foreach-loop