एक फॉरेक्स लूप में शब्दकोश मूल्यों का संपादन


191

मैं एक शब्दकोश से पाई चार्ट बनाने की कोशिश कर रहा हूं। पाई चार्ट प्रदर्शित करने से पहले, मैं डेटा को व्यवस्थित करना चाहता हूं। मैं किसी भी पाई स्लाइस को हटा रहा हूं जो पाई के 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);

जवाबों:


260

एक शब्दकोश में एक मूल्य निर्धारित करना इसकी आंतरिक "संस्करण संख्या" को अपडेट करता है - जो पुनरावृत्ति को अमान्य करता है, और कुंजी या मूल्यों के संग्रह से जुड़ा कोई भी पुनरावृत्ति।

मुझे आपकी बात दिखाई देती है, लेकिन साथ ही यह अजीब होगा कि यदि मूल्यों का संग्रह मध्य पुनरावृत्ति को बदल सकता है - और सादगी के लिए केवल एक संस्करण संख्या है।

इस तरह की चीज़ को ठीक करने का सामान्य तरीका यह है कि या तो पहले से ही चाबियों के संग्रह को कॉपी कर लिया जाए और प्रतिलिपि पर पुनरावृति कर दी जाए, या मूल संग्रह पर पुनरावृति कर दी जाए, लेकिन आपके द्वारा पुनरावृति समाप्त करने के बाद आपके द्वारा लागू किए गए परिवर्तनों का संग्रह बनाए रखा जाएगा।

उदाहरण के लिए:

पहले चाबियाँ कॉपी करना

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 (colStates में स्ट्रिंग कुंजी ।Keys.ToList ()) {...}
Machtyn

6
@Machtyn: ज़रूर - लेकिन सवाल .NET 2.0 के बारे में विशेष रूप से किया गया था, अन्यथा मैं निश्चित रूप से होगा इस्तेमाल किया LINQ की है।
जॉन स्कीट

"संस्करण संख्या" डिक्शनरी की दृश्य स्थिति का हिस्सा है या कार्यान्वयन विस्तार?
बेनामी कायर

@SEinfringescop कॉपीराइट: यह सीधे दिखाई नहीं देता है; तथ्य यह है कि शब्दकोश को अपडेट करने से पुनरावृत्त अमान्य हो जाता है , हालांकि दिखाई देता है।
जॉन स्कीट

से माइक्रोसॉफ्ट डॉक्स .NET फ़्रेमवर्क 4.8 : foreach बयान प्रगणक, जो केवल संग्रह से पढ़ने की अनुमति देता है, यह करने के लिए नहीं लिख के चारों ओर एक आवरण है। इसलिए मैं कहूंगा कि यह एक कार्यान्वयन विवरण है जो भविष्य के संस्करण में बदल सकता है। और जो दिखाई दे रहा है, वह यह है कि एन्यूमरेटर के उपयोगकर्ता ने इसका अनुबंध तोड़ दिया है। लेकिन मैं गलत हूँ ... यह वास्तव में दिखाई देता है जब एक शब्दकोश धारावाहिक है।
बेनामी कायर

81

लूप ToList()में कॉल करें foreach। इस तरह हम एक अस्थायी चर प्रति की जरूरत नहीं है। यह 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;
    }
}

बहुत अच्छा सुधार!
SpeziFish

1
यह foreach(var pair in colStates.ToList())कुंजी और मूल्य तक पहुँचने से बचने के लिए उपयोग करने के लिए बेहतर होगा जो कॉल करने से बचा जाता है colStates[key]..
user2864740

21

आप इस पंक्ति में संग्रह को संशोधित कर रहे हैं:

colStates [कुंजी] = 0;

ऐसा करने से, आप अनिवार्य रूप से उस बिंदु पर कुछ को हटा रहे हैं और पुनर्व्याख्या कर रहे हैं (जहाँ तक IEnumerable का संबंध है वैसे भी।

यदि आप उस मूल्य के सदस्य को संपादित करते हैं जिसे आप संग्रहीत कर रहे हैं, तो यह ठीक होगा, लेकिन आप स्वयं मूल्य को संपादित कर रहे हैं और IEnumberable ऐसा नहीं करता है।

मेरे द्वारा उपयोग किया जाने वाला समाधान फॉरेस्ट लूप को खत्म करना है और सिर्फ लूप के लिए उपयोग करना है। लूप के लिए एक सरल परिवर्तन के लिए जाँच नहीं करेगा कि आप जानते हैं कि यह संग्रह को प्रभावित नहीं करेगा।

यहाँ आप इसे कैसे कर सकते हैं:

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

मुझे यह समस्या लूप के लिए इस्तेमाल हो रही है। शब्दकोश [सूचकांक] [कुंजी] = "एबीसी", लेकिन यह प्रारंभिक मूल्य "xyz" पर वापस लौटता है
निक चान अब्दुल्ला

1
इस कोड में फिक्स लूप के लिए नहीं है: यह कुंजी की सूची की नकल कर रहा है। (यह अभी भी अगर आप इसे एक foreach पाश करने के लिए परिवर्तित काम करेगा।) का उपयोग कर पाश के लिए एक का उपयोग कर का मतलब होगा द्वारा सुलझाने colStates.Keysके स्थान पर keys
इडबरी

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 का उपयोग कर रहा हूं।
ऐहो जूल 1'09

आप इसे 2.0 से System.Core.DLL के 3.5 संस्करण के संदर्भ में उपयोग कर सकते हैं - यदि ऐसा कुछ नहीं है जिसे आप मुझे बताना चाहते हैं और मैं इस उत्तर को हटा दूंगा।
स्कॉट Ivey

1
मैं शायद इस मार्ग पर नहीं जाऊँगा, लेकिन यह एक अच्छा सुझाव है। मेरा सुझाव है कि यदि आप किसी अन्य मामले में उसी मुद्दे पर ठोकर खाते हैं, तो आप उत्तर छोड़ दें।
अन्हो ३:०१

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

आपको जगह में संशोधन करने के बजाय पुराने से एक नया शब्दकोश बनाने की आवश्यकता है। Somethine की तरह (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 से शुरू करके आप समवर्ती के साथ ऐसा कर सकते हैं :

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 # नहीं करता

आप DictionaryEntry ऑब्जेक्ट को संशोधित करने का प्रयास कर रहे हैं जो कि HashTable में संग्रहीत है। हैशटेबल केवल एक वस्तु को संग्रहीत करता है - DictionaryEntry का आपका उदाहरण। कुंजी या मान को बदलना हैशटेबल को बदलने और गणना करने वाले को अमान्य बनाने के लिए पर्याप्त है।

आप इसे लूप के बाहर कर सकते हैं:

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लूप समाधान बहुत कम कुशल है। इसका कारण यह है कि ElementAtओ (एन) पर हैdictionary.Keys संपत्ति , यह संग्रह की शुरुआत से nth आइटम तक पहुंचने तक खोज करता है।

परीक्षा:

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
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.