C # में गणना करते समय सूची <T> से आइटम हटाने का बुद्धिमान तरीका


86

मेरे पास किसी आइटम को संग्रह से निकालने का प्रयास करने का क्लासिक मामला है, जबकि इसे लूप में एन्यूमरेट कर रहा है:

List<int> myIntCollection = new List<int>();
myIntCollection.Add(42);
myIntCollection.Add(12);
myIntCollection.Add(96);
myIntCollection.Add(25);

foreach (int i in myIntCollection)
{
    if (i == 42)
        myIntCollection.Remove(96);    // The error is here.
    if (i == 25)
        myIntCollection.Remove(42);    // The error is here.
}

परिवर्तन के बाद पुनरावृत्ति की शुरुआत में, एक InvalidOperationExceptionफेंक दिया जाता है, क्योंकि अंतर्निहित संग्रह में परिवर्तन होने पर प्रगणक पसंद नहीं करते हैं।

मुझे पुनरावृत्ति करते हुए संग्रह में परिवर्तन करने की आवश्यकता है। कई पैटर्न हैं जिनका उपयोग इससे बचने के लिए किया जा सकता है , लेकिन उनमें से कोई भी एक अच्छा समाधान नहीं है:

  1. इस लूप के अंदर डिलीट न करें, इसके बजाय एक अलग "डिलीट लिस्ट" रखें, जिसे आप मेन लूप के बाद प्रोसेस करते हैं।

    यह आम तौर पर एक अच्छा समाधान है, लेकिन मेरे मामले में, मुझे आइटम को तुरंत "प्रतीक्षा" के रूप में जाने की आवश्यकता है, जब तक कि मुख्य लूप के बाद आइटम वास्तव में मेरे कोड के तर्क प्रवाह को बदल देता है।

  2. आइटम को हटाने के बजाय, बस आइटम पर एक ध्वज सेट करें और इसे निष्क्रिय के रूप में चिह्नित करें। फिर सूची को साफ करने के लिए पैटर्न 1 की कार्यक्षमता जोड़ें।

    यह मेरी सभी जरूरतों के लिए काम करेगा , लेकिन इसका मतलब है कि किसी आइटम के एक्सेस होने पर हर बार निष्क्रिय ध्वज की जांच करने के लिए बहुत सारे कोड को बदलना होगा। यह मेरी पसंद के लिए बहुत अधिक प्रशासन है।

  3. किसी तरह पैटर्न 2 के विचारों को एक वर्ग में शामिल किया जाता है जो इससे उत्पन्न होता है List<T>। यह सुपरलिस्ट निष्क्रिय झंडे को संभाल लेगा, इस तथ्य के बाद वस्तुओं का विलोपन होगा और यह भी गणना उपभोक्ताओं के लिए निष्क्रिय के रूप में चिह्नित वस्तुओं को उजागर नहीं करेगा। मूल रूप से, यह सिर्फ पैटर्न 2 (और बाद के पैटर्न 1) के सभी विचारों को कूटबद्ध करता है।

    क्या इस तरह एक वर्ग मौजूद है? क्या किसी के पास इसके लिए कोड है? या कोई बेहतर तरीका है?

  4. मुझे बताया गया है कि पहुँच के myIntCollection.ToArray()बजाय myIntCollectionसमस्या का समाधान होगा और मुझे लूप के अंदर हटाने की अनुमति देगा।

    यह मेरे लिए एक खराब डिजाइन पैटर्न की तरह लगता है, या शायद यह ठीक है?

विवरण:

  • सूची में कई आइटम होंगे और मैं उनमें से कुछ को ही हटाऊंगा।

  • लूप के अंदर, मैं सभी प्रकार की प्रक्रियाएं कर रहा हूं, जोड़ना, निकालना आदि, इसलिए समाधान को काफी सामान्य होने की आवश्यकता है।

  • जिस आइटम को मुझे हटाने की आवश्यकता है वह लूप में वर्तमान आइटम नहीं हो सकता है। उदाहरण के लिए, मैं 30 आइटम लूप के आइटम 10 पर हो सकता हूं और आइटम 6 या आइटम 26 को हटाने की आवश्यकता हो सकती है। सरणी के माध्यम से पीछे की ओर चलना अब इस वजह से काम नहीं करेगा। ; ओ (


किसी और के लिए संभव उपयोगी जानकारी: संग्रह से बचें त्रुटि को संशोधित किया गया है (पैटर्न 1 का
एनकैप्सुलेशन

एक साइड नोट: सूची में बहुत अधिक समय (आमतौर पर ओ (एन), जहां एन सूची की लंबाई है) मूल्यों को स्थानांतरित करता है। यदि कुशल रैंडम एक्सेस की वास्तव में आवश्यकता है, तो ओ (लॉग एन) में डिलीट को प्राप्त करना संभव है, एक संतुलित बाइनरी ट्री का उपयोग करके सबट्री में नोड्स की संख्या को पकड़े हुए है जिसकी जड़ है। यह एक BST है जिसकी कुंजी (अनुक्रम में सूचकांक) निहित है।
पलक

कृपया उत्तर देखें: stackoverflow.com/questions/7193294/…
डब्बास

जवाबों:


195

सबसे अच्छा समाधान आमतौर पर RemoveAll()विधि का उपयोग करने के लिए है :

myList.RemoveAll(x => x.SomeProp == "SomeValue");

या, यदि आपको हटाए गए कुछ तत्वों की आवश्यकता है:

MyListType[] elems = new[] { elem1, elem2 };
myList.RemoveAll(x => elems.Contains(x));

यह मान लें कि आपका लूप केवल उद्देश्यों को हटाने के उद्देश्य से है। यदि आप ऐसा करने में अतिरिक्त संसाधन की जरूरत है, तो सबसे अच्छा तरीका एक का उपयोग करने के आमतौर पर है forया whileपाश, तब से आप एक प्रगणक उपयोग नहीं कर रहे:

for (int i = myList.Count - 1; i >= 0; i--)
{
    // Do processing here, then...
    if (shouldRemoveCondition)
    {
        myList.RemoveAt(i);
    }
}

पीछे की ओर जाना यह सुनिश्चित करता है कि आप किसी भी तत्व को छोड़ें नहीं।

प्रतिक्रिया के लिए संपादित करें :

यदि आप प्रतीत होता है कि मनमाने ढंग से निकाले गए तत्व हैं, तो सबसे आसान तरीका यह हो सकता है कि जिन तत्वों को आप हटाना चाहते हैं, उन पर नज़र रखें और फिर उन सभी को एक बार में हटा दें। कुछ इस तरह:

List<int> toRemove = new List<int>();
foreach (var elem in myList)
{
    // Do some stuff

    // Check for removal
    if (needToRemoveAnElement)
    {
        toRemove.Add(elem);
    }
}

// Remove everything here
myList.RemoveAll(x => toRemove.Contains(x));

आपकी प्रतिक्रिया के बारे में: मुझे उस आइटम के प्रसंस्करण के दौरान आइटम को तुरंत हटाने की आवश्यकता है न कि पूरे लूप के संसाधित होने के बाद। जो समाधान मैं उपयोग कर रहा हूं, वह उन किसी भी आइटम को पूरा करने के लिए है जिन्हें मैं तुरंत हटाना चाहता हूं और बाद में उन्हें हटाना चाहता हूं। यह एक आदर्श समाधान नहीं है क्योंकि मुझे पूरे स्थान पर NULL के लिए जांच करनी है, लेकिन यह काम नहीं करता है।
जॉन स्टॉक

आश्चर्य है कि अगर 'एलएम' एक इंट नहीं है, तो हम एटाएल का उपयोग नहीं कर सकते हैं जो कि संपादित प्रतिक्रिया कोड पर मौजूद है।
उपयोगकर्ता एम

22

यदि आप दोनों को एन्यूमरेट करना चाहिए List<T>और इसे हटा देना चाहिए तो मेरा सुझाव है कि ए के whileबजाय केवल लूप का उपयोग करेंforeach

var index = 0;
while (index < myList.Count) {
  if (someCondition(myList[index])) {
    myList.RemoveAt(index);
  } else {
    index++;
  }
}

यह मेरी राय में स्वीकृत उत्तर होना चाहिए। यह आपको अपनी सूची के बाकी आइटमों पर विचार करने की अनुमति देता है, बिना हटाए गए आइटम की पुन: पुनरावृति के बिना।
Slvrfn

13

मुझे पता है कि यह पोस्ट पुरानी है, लेकिन मुझे लगा कि मैं वही काम करूंगा जो मेरे लिए काम करता है।

गणना के लिए सूची की एक प्रति बनाएँ, और फिर प्रत्येक लूप के लिए, आप कॉपी किए गए मानों पर प्रक्रिया कर सकते हैं, और स्रोत सूची के साथ / जो भी हटा / जोड़ सकते हैं।

private void ProcessAndRemove(IList<Item> list)
{
    foreach (var item in list.ToList())
    {
        if (item.DeterminingFactor > 10)
        {
            list.Remove(item);
        }
    }
}

".TLList ()" पर महान विचार! सिंपल हेड-स्लैपर, और उन मामलों में भी काम करता है, जहाँ आप स्टॉक "Remove ... ()" मेथ्स को सीधे इस्तेमाल नहीं कर रहे हैं।
आकाशगंगा

1
हालांकि बहुत अक्षम है।
नवाफाल

8

जब आपको एक सूची के माध्यम से पुनरावृत्ति करने की आवश्यकता होती है और इसे लूप के दौरान संशोधित किया जा सकता है, तो आप लूप के लिए उपयोग करना बेहतर होता है:

for (int i = 0; i < myIntCollection.Count; i++)
{
    if (myIntCollection[i] == 42)
    {
        myIntCollection.Remove(i);
        i--;
    }
}

बेशक, आपको सावधान रहना चाहिए, उदाहरण के लिए, iजब भी कोई आइटम हटाया जाता है तो मैं इसे घटाता हूं अन्यथा हम प्रविष्टियां छोड़ देंगे (एक विकल्प यह है कि सूची में पीछे की तरफ जाएं)।

अगर आपके पास Linq है तो आपको बस उपयोग करना चाहिए RemoveAllजैसा कि dlev ने सुझाया है।


केवल तब काम करता है जब आप वर्तमान तत्व को हटा रहे हों। यदि आप एक मनमाना तत्व निकाल रहे हैं, तो आपको यह जाँचने की आवश्यकता होगी कि क्या इसका सूचकांक मौजूदा सूचकांक के पहले या बाद में है या नहीं --i
कंपूसी

मूल प्रश्न ने यह स्पष्ट नहीं किया कि वर्तमान के अलावा अन्य तत्वों को हटाने का समर्थन किया जाना चाहिए, @CompuChip। स्पष्ट होने के बाद से यह उत्तर नहीं बदला है।
पलेक

@Palec, मैं समझता हूं, इसलिए मेरी टिप्पणी।
11

5

जैसे ही आप सूची में शामिल होते हैं, आप को KEEP में एक नई सूची में जोड़ना चाहते हैं। बाद में, नई सूची को असाइन करेंmyIntCollection

List<int> myIntCollection=new List<int>();
myIntCollection.Add(42);
List<int> newCollection=new List<int>(myIntCollection.Count);

foreach(int i in myIntCollection)
{
    if (i want to delete this)
        ///
    else
        newCollection.Add(i);
}
myIntCollection = newCollection;

3

आइए आपको कोड जोड़ते हैं:

List<int> myIntCollection=new List<int>();
myIntCollection.Add(42);
myIntCollection.Add(12);
myIntCollection.Add(96);
myIntCollection.Add(25);

यदि आप सूची में बदलाव करना चाहते हैं, तो आपको टाइप करना होगा .ToList()

foreach(int i in myIntCollection.ToList())
{
    if (i == 42)
       myIntCollection.Remove(96);
    if (i == 25)
       myIntCollection.Remove(42);
}

0

कैसा रहेगा

int[] tmp = new int[myIntCollection.Count ()];
myIntCollection.CopyTo(tmp);
foreach(int i in tmp)
{
    myIntCollection.Remove(42); //The error is no longer here.
}

वर्तमान C # में, इसे foreach (int i in myIntCollection.ToArray()) { myIntCollection.Remove(42); }किसी भी गणना के लिए फिर से लिखा जा सकता है , और List<T>विशेष रूप से .NET 2.0 में भी इस पद्धति का समर्थन करता है।
पलक

0

यदि आप उच्च प्रदर्शन में रुचि रखते हैं, तो आप दो सूचियों का उपयोग कर सकते हैं। निम्नलिखित कचरा संग्रह को कम करता है, मेमोरी लोकलिटी को अधिकतम करता है और वास्तव में कभी भी किसी सूची से कोई आइटम नहीं निकालता है, जो कि अंतिम आइटम नहीं होने पर बहुत अक्षम है।

private void RemoveItems()
{
    _newList.Clear();

    foreach (var item in _list)
    {
        item.Process();
        if (!item.NeedsRemoving())
            _newList.Add(item);
    }

    var swap = _list;
    _list = _newList;
    _newList = swap;
}

0

उन लोगों के लिए जो इसमें मदद कर सकते हैं, मैंने इस एक्सटेंशन विधि को विधेय से मेल खाने वाली वस्तुओं को हटाने और हटाए गए आइटमों की सूची वापस करने के लिए लिखा था।

    public static IList<T> RemoveAllKeepRemoved<T>(this IList<T> source, Predicate<T> predicate)
    {
        IList<T> removed = new List<T>();
        for (int i = source.Count - 1; i >= 0; i--)
        {
            T item = source[i];
            if (predicate(item))
            {
                removed.Add(item);
                source.RemoveAt(i);
            }
        }
        return removed;
    }
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.