Edycja wartości słownika w pętli foreach

Próbuję zbudować wykres kołowy ze słownika. Zanim wyświetlę Wykres kołowy, chcę uporządkować dane. Usuwam wszystkie plasterki ciasta, które byłyby mniej niż 5% ciasta i wkładam je do" innego " kawałka ciasta. Jednak dostaję Collection was modified; enumeration operation may not execute wyjątek w czasie wykonywania.

Rozumiem, dlaczego nie można dodawać lub usuwać elementów ze słownika podczas iteracji nad nimi. Jednak nie rozumiem, dlaczego nie można po prostu zmienić wartości dla istniejącego klucza w foreach pętla.

Wszelkie sugestie re: naprawienie mojego kodu, będą mile widziane.

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);
Author: sǝɯɐſ, 2009-07-01

12 answers

Ustawienie wartości w słowniku aktualizuje jego wewnętrzny "numer wersji" - co unieważnia iterator i każdy iterator powiązany z kolekcją kluczy lub wartości.

Rozumiem twój punkt widzenia, ale jednocześnie byłoby dziwne, gdyby zbiór wartości mógł zmienić się w połowie iteracji - i dla uproszczenia jest tylko jeden numer wersji.

Normalnym sposobem naprawienia tego typu rzeczy jest albo skopiowanie zbioru kluczy wcześniej i iteracja nad kopią, albo iteracja nad oryginalną kolekcją, ale zachowaj kolekcję zmian, które zastosujesz po zakończeniu iteracji.

Na przykład:

Kopiowanie kluczy najpierw

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

Lub...

Tworzenie listy modyfikacji

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;
}
 217
Author: Jon Skeet,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-05-23 18:50:43

Wywołaj ToList() W pętli foreach. W ten sposób nie potrzebujemy kopii zmiennej temp. To zależy od Linq, który jest dostępny od. 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;
    }
}
 52
Author: DIG,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-11-16 02:23:34

Modyfikujesz zbiór w tej linii:

ColStates[key] = 0;

Robiąc to, zasadniczo usuwasz i ponownie wstawiasz coś w tym momencie (jeśli chodzi o IEnumerable, tak czy inaczej.

Jeśli edytujesz member wartości, którą przechowujesz, byłoby to w porządku, ale edytujesz samą wartość i IEnumberable tego nie lubi.

Rozwiązaniem, którego użyłem, jest wyeliminowanie pętli foreach i po prostu użycie pętli for. A pętla simple for Nie sprawdza zmian, o których wiesz, że nie wpłyną na kolekcję.

Oto jak możesz to zrobić:

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;    
    }
}
 17
Author: CodeFusionMobile,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-07-01 19:19:02

Nie można modyfikować kluczy ani wartości bezpośrednio w ForEach, ale można modyfikować ich elementy składowe. Np. powinno działać:

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 } );
 4
Author: Jeremy Frey,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-07-01 19:15:43

Może po prostu zrobimy kilka zapytań linq przeciwko słownikowi, a następnie powiązamy Wykres z wynikami tych zapytań?...

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);
}
 3
Author: Scott Ivey,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-07-01 19:18:57

Jeśli czujesz się kreatywny, możesz zrobić coś takiego. Wykonaj pętlę wstecz przez słownik, aby wprowadzić zmiany.

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

}

Na pewno nie identyczne, ale i tak możesz być zainteresowany...

 2
Author: Hugoware,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-07-01 19:19:08

Musisz utworzyć nowy słownik ze starego, zamiast go modyfikować. Somethine like (również iterate nad KeyValuePair zamiast używać Key lookup:

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
Author: Richard,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-07-01 19:08:43

Nie można modyfikować kolekcji, nawet wartości. Możesz zapisać te sprawy i usunąć je później. Skończyłoby się tak:

        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);
 1
Author: Samuel Carrijo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-07-01 19:08:59

Disclaimer: I don ' t do much C #

Próbujesz zmodyfikować obiekt DictionaryEntry, który jest przechowywany w tabeli HashTable. Hashtable przechowuje tylko jeden obiekt - Twoją instancję DictionaryEntry. Zmiana klucza lub wartości wystarczy, aby zmienić HashTable i spowodować, że wyliczenie stanie się nieważne.

Możesz to zrobić poza pętlą:

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

Najpierw tworząc listę wszystkich kluczy wartości, które chcesz zmienić i iterację przez tę listę zamiast tego.

 0
Author: Cambium,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-07-01 19:08:58

Możesz zrobić listę kopii dict.Values, Następnie możesz użyć funkcji lambda List.ForEach do iteracji (lub pętli foreach, jak sugerowano wcześniej).

new List<string>(myDict.Values).ForEach(str =>
{
  //Use str in any other way you need here.
  Console.WriteLine(str);
});
 0
Author: Nick Louloudakis,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-07-10 14:00:16

Począwszy od. NET 4.5 możesz to zrobić za pomocą 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);

Zauważ jednak, że jego wydajność jest w rzeczywistości znacznie gorsza niż prosta 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>();
    }
}

Wynik:

              Method |      Mean |     Error |    StdDev |
--------------------- |----------:|----------:|----------:|
 ConcurrentDictionary | 182.24 us | 3.1507 us | 2.7930 us |
           Dictionary |  47.01 us | 0.4824 us | 0.4512 us |
 0
Author: Ohad Schneider,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-08-29 15:49:29

Wraz z innymi odpowiedziami, pomyślałem, że zauważę, że jeśli otrzymasz sortedDictionary.Keys LUB sortedDictionary.Values, a następnie zapętlisz je za pomocą foreach, również przejdziesz przez nie w uporządkowanej kolejności. Dzieje się tak dlatego, że metody te zwracają obiekty System.Collections.Generic.SortedDictionary<TKey,TValue>.KeyCollection lub SortedDictionary<TKey,TValue>.ValueCollection, które zachowują rodzaj oryginalnego słownika.

 0
Author: jep,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-08-15 14:52:58