programing

각 루프에 대한 사전 값 편집

topblog 2023. 5. 23. 21:19
반응형

각 루프에 대한 사전 값 편집

저는 사전으로 파이 차트를 만들려고 합니다.파이 차트를 표시하기 전에 데이터를 정리하고 싶습니다.파이의 5% 미만인 파이 조각을 제거하고 "기타" 파이 조각에 넣습니다.하지만 나는 받고 있습니다.Collection was modified; enumeration operation may not execute런타임에 예외가 발생합니다.

항목을 반복하는 동안 사전에서 항목을 추가하거나 제거할 수 없는 이유를 이해합니다.하지만 각 루프에 대한 기존 키의 값을 단순히 변경할 수 없는 이유를 이해할 수 없습니다.

어떤 제안이든 제 코드를 수정해 주시면 감사하겠습니다.

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

사전에서 값을 설정하면 내부 "버전 번호"가 업데이트됩니다. 이 경우 반복기 및 키 또는 값 수집과 관련된 모든 반복기가 비활성화됩니다.

당신의 요점을 이해하지만, 동시에 값 모음이 중간 반복으로 변경될 수 있다면 이상할 것입니다. 그리고 단순화를 위해 버전 번호는 하나뿐입니다.

이러한 문제를 해결하는 일반적인 방법은 키 컬렉션을 사전에 복사하여 복사본 위에 반복하거나 원래 컬렉션 위에서 반복하되 반복 작업을 마친 후 적용할 변경 사항 컬렉션을 유지하는 것입니다.

예:

키를 먼저 복사하는 중

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

에게 하세요.ToList()에 시대에foreach루프. 이렇게 하면 임시 변수 복사본이 필요하지 않습니다.이후에 사용할 수 있는 Linq에 따라 다릅니다.네트 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;
    }
}

다음 줄에서 컬렉션을 수정합니다.

colStates[key] = 0;

그렇게 함으로써, 당신은 본질적으로 그 시점에서 무언가를 삭제하고 다시 삽입하는 것입니다(IEnumberable에 관한 한).

저장 중인 값의 멤버를 편집하는 경우에는 괜찮지만 값 자체를 편집하는 것이므로 IEnumberable은 이를 좋아하지 않습니다.

제가 사용한 솔루션은 각 루프에 대한 for 루프를 제거하고 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;    
    }
}

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

.NET 5에서는 사전이 열거되는 동안 사전 항목을 변경할 수 있습니다.

꺼내기 요청은: 열거사전 덮어쓰기 허용이며 문제는 사전의 덮어쓰기에서 _version++을 제거하는 것입니다.<TKey, TValue >.

이제 다음을 수행할 수 있습니다.

foreach (var pair in dict)
    dict[pair.Key] = pair.Value + 1;

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

만약 여러분이 창의적이라고 느낀다면, 여러분은 이런 것을 할 수 있습니다.사전을 뒤로 돌려 변경합니다.

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

}

확실히 똑같지는 않지만, 어쨌든 관심이 있을 겁니다

제자리에서 수정하는 대신 이전 사전을 새로 만들어야 합니다.(키 검색을 사용하는 대신 키 값 쌍<,>을 통해 반복됩니다.)

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;

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

컬렉션은 수정할 수 없으며 값도 수정할 수 없습니다.이러한 사례를 저장하고 나중에 제거할 수 있습니다.다음과 같은 결과를 초래할 수 있습니다.

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

고지 사항:저는 C#를 많이 하지 않습니다.

해시 테이블에 저장된 DictionaryEntry 개체를 수정하려고 합니다.해시 테이블에는 DictionaryEntry의 인스턴스인 하나의 개체만 저장됩니다.키 또는 값을 변경하면 해시 테이블을 변경하고 열거자를 비활성화하기에 충분합니다.

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

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

먼저 변경할 값의 모든 키 목록을 만들고 해당 목록을 통해 반복합니다.

당신은 의목복을만수다있의 수 .dict.Values그러면 당신은 그것을 사용할 수 있습니다.List.ForEach ( 반에대람또함수다한는복)또함((는▁aa))foreach루프(앞에서 제안한 바와 같이).

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

다른 답변들과 함께, 만약 당신이 그 정보를 얻는다면, 저는 그것을 주목할 것이라고 생각했습니다.sortedDictionary.Keys또는sortedDictionary.Values그리고 나서 그들 위로 루프합니다.foreach또한 정렬된 순서대로 진행합니다.그은 그방법들이되때문니다입기오돌아다때니▁return▁those▁this문을 반환하기 때문입니다.System.Collections.Generic.SortedDictionary<TKey,TValue>.KeyCollection또는SortedDictionary<TKey,TValue>.ValueCollection원본 사전의 종류를 유지 관리하는 개체입니다.

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

다른 답변에서 제안한 것처럼 다른 목록을 만드는 대신 다음을 사용할 수 있습니다.for 사전을사루기하프여하용▁using를 하여 루프하기Count 및 루프정 의경건우조에 대해Keys.ElementAt(i)열쇠를 가지러 왔습니다.

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

처음에는 키 목록을 작성할 필요가 없기 때문에 이것이 더 효율적일 것이라고 생각했습니다.테스트를 실행한 후에 나는 그것을 발견했습니다.for루프 솔루션은 훨씬 덜 효율적입니다.그 이유는ElementAt의 O(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

언급URL : https://stackoverflow.com/questions/1070766/editing-dictionary-values-in-a-foreach-loop

반응형