在foreach循环中编辑字典值

时间:2022-10-25 08:38:23

I am trying to build a pie chart from a dictionary. Before I display the pie chart, I want to tidy up the data. I'm removing any pie slices that would be less than 5% of the pie and putting them in a "Other" pie slice. However I'm getting a Collection was modified; enumeration operation may not execute exception at runtime.

我正试图从字典中建立一个饼状图。在显示饼状图之前,我需要整理数据。我将移除任何小于5%的饼状图,并将它们放入一个“其他”的饼片中。但是我得到的集合被修改了;枚举操作在运行时可能不执行异常。

I understand why you can not add or remove items from a dictionary while iterating over them. However I don't understand why you can't simply change a value for an existing key within the foreach loop.

我理解为什么在遍历字典时不能添加或删除条目。但是我不明白为什么不能简单地在foreach循环中更改现有键的值。

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

任何建议re:修改我的代码,将会被欣赏。

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

11 个解决方案

#1


209  

Setting a value in a dictionary updates its internal "version number" - which invalidates the iterator, and any iterator associated with the keys or values collection.

在字典中设置一个值将更新它的内部“版本号”——这会使迭代器和与键或值集合相关联的任何迭代器失效。

I do see your point, but at the same time it would be odd if the values collection could change mid-iteration - and for simplicity there's only one version number.

我确实理解您的观点,但与此同时,如果值集合可以在迭代中期更改,那么这将是奇怪的——为了简单起见,只有一个版本号。

The normal way of fixing this sort of thing is to either copy the collection of keys beforehand and iterate over the copy, or iterate over the original collection but maintain a collection of changes which you'll apply after 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 list of modifications

创建修改列表

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

#2


47  

Call the ToList() in the foreach loop. This way we dont need a temp variable copy. It depends on Linq which is available since .Net 3.5.

在foreach循环中调用ToList()。这样我们就不需要临时变量拷贝了。这取决于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;
    }
}

#3


17  

You are modifying the collection in this line:

您正在修改这一行中的集合:

colStates[key] = 0;

colStates(例子)= 0;

By doing so, you are essentially deleting and reinserting something at that point (as far as IEnumerable is concerned anyways.

通过这样做,您实际上是在删除和重新插入某个东西(就IEnumerable而言,无论如何,IEnumerable是可枚举的)。

If you edit a member of the value you are storing, that would be OK, but you are editing the value itself and IEnumberable doesn't like that.

如果您编辑要存储的值的一个成员,那是可以的,但是您正在编辑值本身,而IEnumberable不喜欢这样。

The solution I've used is to eliminate the foreach loop and just use a for loop. A simple for loop won't check for changes that you know won't effect the collection.

我使用的解决方案是消除foreach循环,只使用for循环。一个简单的for循环不会检查您知道不会影响集合的更改。

Here's how you could do 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;    
    }
}

#4


4  

You can't modify the keys nor the values directly in a ForEach, but you can modify their members. E.g., this should work:

您不能在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 } );

#5


3  

How about just doing some linq queries against your dictionary, and then binding your graph to the results of those?...

对字典进行一些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);
}

#6


2  

If you're feeling creative you could do something like this. Loop backwards through the dictionary to make your changes.

如果你有创造力,你可以做这样的事情。循环遍历字典以进行更改。

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

}

Certainly not identical, but you might be interested anyways...

当然不是完全相同,但是你可能会感兴趣……

#7


1  

You need to create a new Dictionary from the old rather than modifying in place. Somethine like (also iterate over the KeyValuePair<,> rather than using a key lookup:

您需要从旧的字典中创建一个新的字典,而不是在适当的地方进行修改。例如(也可以遍历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;

#8


1  

You can't modify the collection, not even the values. You could save these cases and remove them later. It would end up like this:

不能修改集合,甚至不能修改值。您可以保存这些案例并稍后删除它们。结果是这样的:

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

#9


0  

Disclaimer: I don't do much C#

免责声明:我没有做太多c#

You are trying to modify the DictionaryEntry object which is stored in the HashTable. The Hashtable only stores one object -- your instance of DictionaryEntry. Changing the Key or the Value is enough to change the HashTable and cause the enumerator to become invalid.

您正在尝试修改存储在散列表中的DictionaryEntry对象。Hashtable只存储一个对象——DictionaryEntry的实例。更改键或值就足以更改哈希表并导致枚举数无效。

You can do it outside of the loop:

你可以在循环之外做:

if(hashtable.Contains(key))
{
    hashtable[key] = value;
}

by first creating a list of all the keys of the values you wish to change and iterate through that list instead.

首先创建要更改的值的所有键的列表,然后迭代该列表。

#10


0  

You can make a list copy of the dict.Values, then you can use the List.ForEach lambda function for iteration, (or a foreach loop, as suggested before).

您可以制作一个dict.Values的列表拷贝,然后您可以使用这个列表。对于迭代的每个lambda函数,(或一个ForEach循环,如前所述)。

new List<string>(myDict.Values).ForEach(str =>
{
  //Use str in any other way you need here.
  Console.WriteLine(str);
});

#11


0  

Starting with .NET 4.5 You can do this with ConcurrentDictionary:

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

Note however that its performance is actually much worse that a simple foreach dictionary.Kes.ToArray():

但是,请注意,它的性能实际上比一个简单的foreach dictionary.c . 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>();
    }
}

Result:

结果:

              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


209  

Setting a value in a dictionary updates its internal "version number" - which invalidates the iterator, and any iterator associated with the keys or values collection.

在字典中设置一个值将更新它的内部“版本号”——这会使迭代器和与键或值集合相关联的任何迭代器失效。

I do see your point, but at the same time it would be odd if the values collection could change mid-iteration - and for simplicity there's only one version number.

我确实理解您的观点,但与此同时,如果值集合可以在迭代中期更改,那么这将是奇怪的——为了简单起见,只有一个版本号。

The normal way of fixing this sort of thing is to either copy the collection of keys beforehand and iterate over the copy, or iterate over the original collection but maintain a collection of changes which you'll apply after 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 list of modifications

创建修改列表

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

#2


47  

Call the ToList() in the foreach loop. This way we dont need a temp variable copy. It depends on Linq which is available since .Net 3.5.

在foreach循环中调用ToList()。这样我们就不需要临时变量拷贝了。这取决于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;
    }
}

#3


17  

You are modifying the collection in this line:

您正在修改这一行中的集合:

colStates[key] = 0;

colStates(例子)= 0;

By doing so, you are essentially deleting and reinserting something at that point (as far as IEnumerable is concerned anyways.

通过这样做,您实际上是在删除和重新插入某个东西(就IEnumerable而言,无论如何,IEnumerable是可枚举的)。

If you edit a member of the value you are storing, that would be OK, but you are editing the value itself and IEnumberable doesn't like that.

如果您编辑要存储的值的一个成员,那是可以的,但是您正在编辑值本身,而IEnumberable不喜欢这样。

The solution I've used is to eliminate the foreach loop and just use a for loop. A simple for loop won't check for changes that you know won't effect the collection.

我使用的解决方案是消除foreach循环,只使用for循环。一个简单的for循环不会检查您知道不会影响集合的更改。

Here's how you could do 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;    
    }
}

#4


4  

You can't modify the keys nor the values directly in a ForEach, but you can modify their members. E.g., this should work:

您不能在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 } );

#5


3  

How about just doing some linq queries against your dictionary, and then binding your graph to the results of those?...

对字典进行一些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);
}

#6


2  

If you're feeling creative you could do something like this. Loop backwards through the dictionary to make your changes.

如果你有创造力,你可以做这样的事情。循环遍历字典以进行更改。

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

}

Certainly not identical, but you might be interested anyways...

当然不是完全相同,但是你可能会感兴趣……

#7


1  

You need to create a new Dictionary from the old rather than modifying in place. Somethine like (also iterate over the KeyValuePair<,> rather than using a key lookup:

您需要从旧的字典中创建一个新的字典,而不是在适当的地方进行修改。例如(也可以遍历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;

#8


1  

You can't modify the collection, not even the values. You could save these cases and remove them later. It would end up like this:

不能修改集合,甚至不能修改值。您可以保存这些案例并稍后删除它们。结果是这样的:

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

#9


0  

Disclaimer: I don't do much C#

免责声明:我没有做太多c#

You are trying to modify the DictionaryEntry object which is stored in the HashTable. The Hashtable only stores one object -- your instance of DictionaryEntry. Changing the Key or the Value is enough to change the HashTable and cause the enumerator to become invalid.

您正在尝试修改存储在散列表中的DictionaryEntry对象。Hashtable只存储一个对象——DictionaryEntry的实例。更改键或值就足以更改哈希表并导致枚举数无效。

You can do it outside of the loop:

你可以在循环之外做:

if(hashtable.Contains(key))
{
    hashtable[key] = value;
}

by first creating a list of all the keys of the values you wish to change and iterate through that list instead.

首先创建要更改的值的所有键的列表,然后迭代该列表。

#10


0  

You can make a list copy of the dict.Values, then you can use the List.ForEach lambda function for iteration, (or a foreach loop, as suggested before).

您可以制作一个dict.Values的列表拷贝,然后您可以使用这个列表。对于迭代的每个lambda函数,(或一个ForEach循环,如前所述)。

new List<string>(myDict.Values).ForEach(str =>
{
  //Use str in any other way you need here.
  Console.WriteLine(str);
});

#11


0  

Starting with .NET 4.5 You can do this with ConcurrentDictionary:

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

Note however that its performance is actually much worse that a simple foreach dictionary.Kes.ToArray():

但是,请注意,它的性能实际上比一个简单的foreach dictionary.c . 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>();
    }
}

Result:

结果:

              Method |      Mean |     Error |    StdDev |
--------------------- |----------:|----------:|----------:|
 ConcurrentDictionary | 182.24 us | 3.1507 us | 2.7930 us |
           Dictionary |  47.01 us | 0.4824 us | 0.4512 us |