प्रत्येक लूप के लिए एक डिजाइन दोष के लिए एक संग्रह को संशोधित करने के लिए एक नई सूची बना रहा है?


14

मैं हाल ही Collection was modifiedमें C # में इस सामान्य अमान्य ऑपरेशन में भाग गया , और जब मैं इसे पूरी तरह से समझता हूं, तो यह एक ऐसी सामान्य समस्या लगती है (Google, लगभग 300k परिणाम!)। लेकिन जब आप इसके माध्यम से जाते हैं तो किसी सूची को संशोधित करना एक तार्किक और सीधी बात लगती है।

List<Book> myBooks = new List<Book>();

public void RemoveAllBooks(){
    foreach(Book book in myBooks){
         RemoveBook(book);
    }
}

RemoveBook(Book book){
    if(myBooks.Contains(book)){
         myBooks.Remove(book);
         if(OnBookEvent != null)
            OnBookEvent(this, new EventArgs("Removed"));
    }
}

कुछ लोग इसके माध्यम से पुनरावृति करने के लिए एक और सूची बनाएंगे, लेकिन यह सिर्फ इस मुद्दे को चकमा दे रहा है। वास्तविक समाधान क्या है, या यहां वास्तविक डिजाइन मुद्दा क्या है? हम सभी ऐसा करना चाहते हैं, लेकिन क्या यह एक डिजाइन दोष का संकेत है?


आप सूची के अंत से एक पुनरावृत्ति का उपयोग कर सकते हैं या हटा सकते हैं।
ElDuderino

@ElDuderino msdn.microsoft.com/en-us/library/dscyy5s0.aspxYou consume an iterator from client code by using a For Each…Next (Visual Basic) or foreach (C#) statementलाइन याद नहीं है
SJuan76

जावा में Iterator(आपके बताए अनुसार ListIteratorकाम करता है ) और (आपकी इच्छानुसार काम करता है)। यह इंगित करेगा कि संग्रह प्रकारों और कार्यान्वयनों की एक विस्तृत श्रृंखला पर पुनरावृत्ति कार्यक्षमता प्रदान करने के लिए, Iteratorअत्यंत प्रतिबंधक है (यदि आप एक संतुलित पेड़ में एक नोड को हटाते हैं तो क्या होता है? इसका next()कोई मतलब नहीं है), ListIteratorकम होने के कारण अधिक शक्तिशाली होने के साथ ? बक्सों का इस्तेमाल करें।
SJuan76

@ अन्य भाषाओं में SJuan76 अभी भी अधिक इट्रेटर प्रकार हैं, उदाहरण के लिए C ++ में फॉरवर्ड इटरेटर (जावा के Iterator के बराबर), द्विदिश पुनरावृत्तियों (जैसे ListIterator), यादृच्छिक अभिगम पुनरावृत्तियों, इनपुट पुनरावृत्तियों (एक जावा Iterator की तरह) है जो वैकल्पिक संचालन का समर्थन नहीं करता है ) और आउटपुट पुनरावृत्तियों (यानी केवल लिखने के लिए, जो कि लगता है की तुलना में अधिक उपयोगी है)।
जूल्स

जवाबों:


10

प्रत्येक लूप के लिए एक डिजाइन दोष के लिए एक संग्रह को संशोधित करने के लिए एक नई सूची बना रहा है?

संक्षिप्त उत्तर: नहीं

सीधे शब्दों में, आप एक अपरिभाषित व्यवहार का उत्पादन करते हैं, जब आप एक संग्रह के माध्यम से पुनरावृति करते हैं और उसी समय इसे संशोधित करते हैं। एक क्रम में तत्व को हटाने के बारे में सोचें next। क्या होगा, अगर MoveNext()कहा जाता है?

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

स्रोत: MSDN

वैसे, आप अपने RemoveAllBooksबस को छोटा कर सकते हैंreturn new List<Book>()

और एक पुस्तक निकालने के लिए, मैं एक फ़िल्टर्ड संग्रह को वापस करने की सलाह देता हूँ:

return books.Where(x => x.Author != "Bob").ToList();

एक संभावित शेल्फ-कार्यान्वयन जैसा दिखेगा:

public class Shelf
{
    List<Book> books=new List<Book> {
        new Book ("Paul"),
        new Book ("Peter")
    };

    public IEnumerable<Book> getAllBooks(){
        foreach(Book b in books){
            yield return b;
        }
    }

    public void RemovePetersBooks(){
        books= books.Where(x=>x.Author!="Peter").ToList();
    }

    public void EmptyShelf(){
        books = new List<Book> ();
    }

    public Shelf ()
    {
    }
}

public static void Main (string[] args)
{
    Shelf s = new Shelf ();
    foreach (Book b in s.getAllBooks()) {
        Console.WriteLine (b.Author);
    }
    s.RemovePetersBooks ();

    foreach (Book b in s.getAllBooks()) {
        Console.WriteLine (b.Author);
    }
    s.EmptyShelf ();
    foreach (Book b in s.getAllBooks()) {
        Console.WriteLine (b.Author);
    }
}

जब आप हां का जवाब देते हैं तो आप खुद का विरोध करते हैं और फिर एक उदाहरण प्रदान करते हैं जो एक नई सूची भी बनाता है। इसके अलावा मैं सहमत हूं।
एसेन स्कोव पेडर्सन

1
@EsbenSkovPedersen आप सही हैं: DI एक दोष के रूप में संशोधित करने की सोच रहा था ...
थॉमस जंक

6

इसे संशोधित करने के लिए एक नई सूची बनाना समस्या का एक अच्छा समाधान है कि सूची के मौजूदा पुनरावृत्तियों को परिवर्तन के बाद मज़बूती से जारी नहीं रखा जा सकता है जब तक कि वे नहीं जानते कि सूची कैसे बदल गई है।

एक अन्य उपाय यह है कि सूची इंटरफेस के माध्यम से बजाय इटरेटर का उपयोग करके संशोधन करना। यह केवल तभी काम करता है जब केवल एक पुनरावृत्त हो सकता है - यह समवर्ती सूची में पुनरावृत्ति करने वाले कई थ्रेड्स के लिए समस्या का समाधान नहीं करता है, जो (जब तक बासी डेटा तक पहुंच स्वीकार्य है) एक नई सूची बनाता है।

एक वैकल्पिक समाधान जो फ्रेमवर्क कार्यान्वयनकर्ता ले सकता था, वह यह है कि सूची उसके सभी पुनरावृत्तियों को ट्रैक करे और परिवर्तन होने पर उन्हें सूचित करे। हालांकि, इस दृष्टिकोण के thw ओवरहेड्स उच्च हैं - इसके लिए सामान्य रूप से कई थ्रेड्स के साथ काम करने के लिए, सभी पुनरावृत्तियों के संचालन को सूची को लॉक करने की आवश्यकता होगी (यह सुनिश्चित करने के लिए कि ऑपरेशन प्रगति पर नहीं है), जो सभी सूची बना देगा संचालन धीमा। वहाँ भी स्मृति स्मृति उपरिव्यय किया जाएगा, प्लस यह नरम संदर्भ का समर्थन करने के लिए क्रम की आवश्यकता है, और CLR का पहला संस्करण IIRC नहीं था।

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

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