foreach 루프에서 사전 값 편집


192

사전에서 원형 차트를 작성하려고합니다. 원형 차트를 표시하기 전에 데이터를 정리하고 싶습니다. 파이의 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);

답변:


262

사전에 값을 설정하면 내부 "버전 번호"가 업데이트되어 반복자와 키 또는 값 콜렉션과 연관된 반복자가 무효화됩니다.

나는 당신의 요점을 알지만, 동시에 값 컬렉션이 반복 도중에 변경 될 수 있다면 이상 할 것입니다-그리고 단순화를 위해 버전 번호가 하나뿐입니다.

이러한 종류의 문제를 해결하는 일반적인 방법은 키 컬렉션을 미리 복사하고 복사본을 반복하거나 원본 컬렉션을 반복하지만 반복을 마친 후에 적용 할 변경 사항 컬렉션을 유지하는 것입니다.

예를 들면 다음과 같습니다.

키를 먼저 복사

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

24
나는 이것이 오래되었다는 것을 알고 있지만 .NET 3.5 (또는 4.0입니까?)를 사용하는 경우 다음과 같이 LINQ를 사용하고 남용 할 수 있습니다. foreach (string key in colStates.Keys.ToList ()) {...}
Machtyn

6
@Machtyn : 물론-.NET 2.0에 관한 질문이었습니다. 그렇지 않으면 LINQ를 사용했을 것 입니다 .
Jon Skeet

"버전 번호"는 사전의 가시적 상태 또는 구현 세부 사항의 일부입니까?
익명 겁쟁이

@SEinfringescopyright : 직접 보이지 않습니다. 사전을 업데이트하면 반복자 무효화된다는 사실 볼 수 있습니다.
Jon Skeet

에서 마이크로 소프트 문서 .NET 프레임 워크 4.8 : foreach는 문에만 쓰기, 컬렉션에서하지 읽을 수있는 열거, 래퍼입니다. 따라서 향후 버전에서 변경 될 수있는 구현 세부 사항이라고 말하고 싶습니다. 그리고 보이는 것은 열거 자의 사용자가 계약을 위반 한 것입니다. 그러나 나는 틀릴 것입니다 ... 사전이 직렬화되면 실제로 볼 수 있습니다.
익명 겁쟁이

81

전화 ToList()foreach루프. 이렇게하면 임시 변수 사본이 필요하지 않습니다. .Net 3.5부터 사용 가능한 Linq에 따라 다릅니다.

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

아주 좋은 개선!
SpeziFish

1
foreach(var pair in colStates.ToList()) 값에 액세스 할 수 없도록 하는 것이 좋습니다 colStates[key].
user2864740

21

이 줄에서 컬렉션을 수정하고 있습니다.

colStates [키] = 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;    
    }
}

for 루프를 사용 하여이 문제가 발생합니다. 그것은 "XYZ"초기 값으로 다시 복귀하지만 사전 [인덱스] [키] = "ABC"
닉 찬 압둘라

1
이 코드의 수정은 for 루프가 아니며 키 목록을 복사하는 것입니다. (당신이 foreach 루프로 변환 할 경우 여전히 작동합니다.) for 루프를 사용하여 의미를 사용하여 해결 colStates.Keys대신에 keys.
idbrii

6

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

3

사전에 대해 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);
}

Linq는 3.5에서만 사용할 수 있습니까? .net 2.0을 사용하고 있습니다.
Aheho

3.5 버전의 System.Core.DLL을 참조하여 2.0에서 사용할 수 있습니다-그것이 당신이 수행하고 싶지 않은 것이면이 답변을 삭제하겠습니다.
Scott Ivey

1
아마이 길을 가지 않을 것이지만 그럼에도 불구하고 좋은 제안입니다. 같은 문제를 가진 다른 사람이 우연히 발견되는 경우에 대비하여 답을 남겨 두는 것이 좋습니다.
Aheho

3

창의력이 있다면 이와 같은 일을 할 수 있습니다. 사전을 거꾸로 반복하여 변경하십시오.

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

}

확실히 동일하지는 않지만 어쨌든 관심이있을 수 있습니다 ...


2

기존 위치에서 수정하지 않고 새 사전을 작성해야합니다. 키 조회를 사용하지 않고 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;

1

.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 |

1

값은 물론 컬렉션도 수정할 수 없습니다. 이 사례를 저장하고 나중에 제거 할 수 있습니다. 다음과 같이 끝납니다.

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

컬렉션을 수정할 수 있습니다 . 당신은 할 수없는 수정 컬렉션에 반복자를 계속 사용.
user2864740

0

면책 조항 : 나는 C #을별로하지 않습니다.

HashTable에 저장된 DictionaryEntry 개체를 수정하려고합니다. Hashtable은 DictionaryEntry 인스턴스 하나의 개체 만 저장합니다. 키 또는 값을 변경하면 HashTable을 변경하고 열거자가 유효하지 않게됩니다.

루프 외부에서 할 수 있습니다.

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

먼저 변경하려는 값의 모든 키 목록을 작성하고 해당 목록을 반복하십시오.


0

의 목록 복사본을 dict.Values만든 다음 List.ForEach반복에 람다 함수 (또는 foreach이전에 제안 된 루프)를 사용할 수 있습니다.

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

0

다른 답변과 함께, 나는 당신이 얻을 경우주의라고 생각 sortedDictionary.Keys하거나 sortedDictionary.Values그들을 통해 다음 루프를하고 foreach, 당신은 또한 정렬 된 순서를 통해 이동합니다. 그 메소드 는 원래 사전의 정렬을 유지하는 오브젝트 System.Collections.Generic.SortedDictionary<TKey,TValue>.KeyCollection또는 리턴을 리턴하기 때문 SortedDictionary<TKey,TValue>.ValueCollection입니다.


0

이 답변은 제안 된 솔루션이 아닌 두 솔루션을 비교하기위한 것입니다.

다른 답변이 제안한대로 다른 목록을 만드는 대신 루프 중지 조건에 대한 for사전 Count을 사용하여 루프를 사용 Keys.ElementAt(i)하고 키를 얻을 수 있습니다.

for (int i = 0; i < dictionary.Count; i++)
{
    dictionary[dictionary.Keys.ElementAt(i)] = 0;
}

전나무에서는 키 목록을 만들 필요가 없기 때문에 이것이 더 효율적이라고 생각했습니다. 테스트를 실행 한 후 for루프 솔루션이 훨씬 덜 효율적 이라는 것을 알았습니다 . 그 이유는 속성에서 ElementAtO (n) 이기 때문에 dictionary.Keys컬렉션의 시작 부분부터 n 번째 항목에 도달 할 때까지 검색합니다.

테스트:

int iterations = 10;
int dictionarySize = 10000;
Stopwatch sw = new Stopwatch();

Console.WriteLine("Creating dictionary...");
Dictionary<string, int> dictionary = new Dictionary<string, int>(dictionarySize);
for (int i = 0; i < dictionarySize; i++)
{
    dictionary.Add(i.ToString(), i);
}
Console.WriteLine("Done");

Console.WriteLine("Starting tests...");

// for loop test
sw.Restart();
for (int i = 0; i < iterations; i++)
{
    for (int j = 0; j < dictionary.Count; j++)
    {
        dictionary[dictionary.Keys.ElementAt(j)] = 3;
    }
}
sw.Stop();
Console.WriteLine($"for loop Test:     {sw.ElapsedMilliseconds} ms");

// foreach loop test
sw.Restart();
for (int i = 0; i < iterations; i++)
{
    foreach (string key in dictionary.Keys.ToList())
    {
        dictionary[key] = 3;
    }
}
sw.Stop();
Console.WriteLine($"foreach loop Test: {sw.ElapsedMilliseconds} ms");

Console.WriteLine("Done");

결과 :

Creating dictionary...
Done
Starting tests...
for loop Test:     2367 ms
foreach loop Test: 3 ms
Done
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.