पुनरावृति करते हुए संग्रह से तत्वों को निकालें


215

AFAIK, दो दृष्टिकोण हैं:

  1. संग्रह की एक प्रति पर Iterate
  2. वास्तविक संग्रह के पुनरावृत्त का उपयोग करें

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

List<Foo> fooListCopy = new ArrayList<Foo>(fooList);
for(Foo foo : fooListCopy){
    // modify actual fooList
}

तथा

Iterator<Foo> itr = fooList.iterator();
while(itr.hasNext()){
    // modify actual fooList using itr.remove()
}

क्या किसी एक दृष्टिकोण को दूसरे पर प्राथमिकता देना पसंद है (जैसे पठनीयता के सरल कारण के लिए पहला दृष्टिकोण पसंद करना)?


1
बस जिज्ञासु, आप पहले उदाहरण में मूर्ख के माध्यम से केवल लूपिंग के बजाय मूर्ख की एक प्रति क्यों बनाते हैं?
हज

@ हज़, इसलिए मुझे केवल एक बार लूप करना है।
user1329572

15
नोट: 'जबकि' भी iterators साथ चर का दायरा सीमित करने के लिए खत्म हो गया 'के लिए' पसंद करते हैं: के लिए (इटरेटर <फू> आईटीआर = fooList.iterator (); itr.hasNext ();) {}
puce

मुझे नहीं पता whileथा कि अलग अलग नियम थेfor
अलेक्जेंडर मिल्स

अधिक जटिल स्थिति में आपके पास एक ऐसा मामला हो सकता है जहां fooListएक उदाहरण चर है और आप लूप के दौरान एक विधि को कॉल करते हैं जो उसी कक्षा में दूसरी विधि को कॉल करता है fooList.remove(obj)। ऐसा होते देखा है। किस मामले में सूची की नकल करना सबसे सुरक्षित है।
डेव ग्रिफ़िथ्स

जवाबों:


418

कुछ से बचने के लिए कुछ विकल्पों के साथ कुछ उदाहरण देता हूं ConcurrentModificationException

मान लीजिए हमारे पास पुस्तकों का निम्नलिखित संग्रह है

List<Book> books = new ArrayList<Book>();
books.add(new Book(new ISBN("0-201-63361-2")));
books.add(new Book(new ISBN("0-201-63361-3")));
books.add(new Book(new ISBN("0-201-63361-4")));

लीजिए और निकालें

पहली तकनीक में उन सभी वस्तुओं को इकट्ठा करना शामिल है जिन्हें हम हटाना चाहते हैं (जैसे कि लूप के लिए बढ़ाया का उपयोग करके) और पुनरावृति समाप्त करने के बाद, हम सभी मिली हुई वस्तुओं को हटा देते हैं।

ISBN isbn = new ISBN("0-201-63361-2");
List<Book> found = new ArrayList<Book>();
for(Book book : books){
    if(book.getIsbn().equals(isbn)){
        found.add(book);
    }
}
books.removeAll(found);

यह मानकर चल रहा है कि आप जो ऑपरेशन करना चाहते हैं वह "डिलीट" है।

यदि आप "जोड़ना" चाहते हैं, तो यह दृष्टिकोण भी काम करेगा, लेकिन मैं आपको एक अलग संग्रह पर यह निर्धारित करने के लिए बताऊंगा कि आप किन तत्वों को दूसरे संग्रह में जोड़ना चाहते हैं और फिर addAllअंत में एक विधि जारी करें ।

ListIterator का उपयोग करना

यदि आप सूचियों के साथ काम कर रहे हैं, तो एक और तकनीक का उपयोग किया जाता है ListIteratorजिसमें पुनरावृत्ति के दौरान वस्तुओं को हटाने और जोड़ने के लिए समर्थन होता है।

ListIterator<Book> iter = books.listIterator();
while(iter.hasNext()){
    if(iter.next().getIsbn().equals(isbn)){
        iter.remove();
    }
}

फिर, मैंने ऊपर दिए गए उदाहरण में "हटाएं" विधि का उपयोग किया, जो कि आपके प्रश्न को स्पष्ट रूप से प्रतीत होता है, लेकिन आप addपुनरावृत्ति के दौरान नए तत्वों को जोड़ने के लिए इसकी विधि का उपयोग भी कर सकते हैं ।

JDK> = 8 का उपयोग करना

जावा 8 या बेहतर संस्करणों के साथ काम करने वालों के लिए, कुछ अन्य तकनीकें हैं जिनका आप इसका लाभ उठा सकते हैं।

आप बेस क्लास removeIfमें नई विधि का उपयोग कर सकते हैं Collection:

ISBN other = new ISBN("0-201-63361-2");
books.removeIf(b -> b.getIsbn().equals(other));

या नई स्ट्रीम API का उपयोग करें:

ISBN other = new ISBN("0-201-63361-2");
List<Book> filtered = books.stream()
                           .filter(b -> b.getIsbn().equals(other))
                           .collect(Collectors.toList());

इस अंतिम स्थिति में, किसी संग्रह से तत्वों को फ़िल्टर करने के लिए, आप फ़िल्टर किए गए संग्रह (यानी books = filtered) के मूल संदर्भ को पुन: असाइन करते हैं या फ़िल्टर किए गए संग्रह को removeAllमूल संग्रह (यानी books.removeAll(filtered)) से मिले तत्वों में उपयोग करते हैं ।

सबलिस्ट या सबसेट का उपयोग करें

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

books.subList(0,5).clear();

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

NavigableSet.subSetविधि का उपयोग करके सॉर्ट किए गए सेट के साथ कुछ समान हासिल किया जा सकता है , या वहां की पेशकश की गई किसी भी टुकड़ा करने की विधि।

बातें:

आप किस विधि का उपयोग करते हैं, इस पर निर्भर हो सकता है कि आप क्या करने का इरादा कर रहे हैं

  • संग्रह और removeAl तकनीक किसी भी संग्रह (संग्रह, सूची, सेट, आदि) के साथ काम करती है।
  • ListIteratorतकनीक स्पष्ट रूप से केवल प्रदान की उनकी यह देखते हुए कि, सूचियों के साथ काम करता ListIteratorकार्यान्वयन प्रदान करता है जोड़ और निकाल संचालन के लिए समर्थन करते हैं।
  • Iteratorदृष्टिकोण संग्रह के किसी भी प्रकार के साथ काम करेंगे, लेकिन यह केवल निकालने के संचालन का समर्थन करता है।
  • जब तक हम इसे हटाते हैं, तब तक ListIterator/ के साथ Iteratorस्पष्ट लाभ कुछ भी कॉपी करने के लिए नहीं है। तो, यह बहुत ही कुशल है।
  • जेडीके 8 स्ट्रीम उदाहरण वास्तव में कुछ भी नहीं हटाते हैं, लेकिन वांछित तत्वों की तलाश करते हैं, और फिर हमने मूल संग्रह संदर्भ को नए के साथ बदल दिया, और पुराने को कचरा एकत्र करने दें। इसलिए, हम संग्रह पर केवल एक बार पुनरावृत्ति करते हैं और यह कुशल होगा।
  • संग्रह में और removeAll नुकसान यह है कि हमें दो बार पुनरावृति करनी होगी। पहले हम फुर-लूप में एक ऐसी वस्तु की खोज करते हैं जो हमारे निष्कासन मानदंडों से मेल खाती है, और एक बार जब हम इसे पा लेते हैं, तो हम इसे मूल संग्रह से हटाने के लिए कहते हैं, जो इस आइटम को देखने के लिए दूसरा पुनरावृत्ति कार्य होगा। इसे हटा दो।
  • मुझे लगता है कि यह ध्यान देने योग्य है कि Iteratorइंटरफ़ेस की निष्कासन विधि को Javadocs में "वैकल्पिक" के रूप में चिह्नित किया गया है, जिसका अर्थ है कि ऐसे Iteratorकार्यान्वयन हो सकते हैं जो UnsupportedOperationExceptionहम हटाने की विधि को लागू करते हैं। जैसे, मैं कहूंगा कि यह दृष्टिकोण दूसरों की तुलना में कम सुरक्षित है अगर हम तत्वों को हटाने के लिए पुनरावृत्त समर्थन की गारंटी नहीं दे सकते हैं।

वाहवाही! यह निश्चित गाइड है।
मैग्नो सी

यह एक सही जवाब है! धन्यवाद।
विल्हेम

7
JDK8 धाराओं के बारे में अपने पैराग्राफ में आप का उल्लेख करते हैं removeAll(filtered)। उसके लिए एक शॉर्टकट होगाremoveIf(b -> b.getIsbn().equals(other))
ifloop

Iterator और ListIterator के बीच क्या अंतर है?
अलेक्जेंडर मिल्स

निष्कासन पर विचार नहीं किया गया, लेकिन यह मेरी प्रार्थना का जवाब था। धन्यवाद!
अकबेल


13

क्या कोई कारण है कि दूसरे पर एक दृष्टिकोण को प्राथमिकता दी जाए

पहला दृष्टिकोण काम करेगा, लेकिन सूची की नकल करने के लिए स्पष्ट ओवरहेड है।

दूसरा दृष्टिकोण काम नहीं करेगा क्योंकि कई कंटेनर पुनरावृत्ति के दौरान संशोधन की अनुमति नहीं देते हैं। इसमें शामिल हैंArrayList

यदि वर्तमान तत्व को हटाने के लिए एकमात्र संशोधन है, तो आप उपयोग करके दूसरा दृष्टिकोण कार्य कर सकते हैं itr.remove()(अर्थात, इट्रेटर की remove()विधि का उपयोग करें , कंटेनर का नहीं )। यह पुनरावृत्तियों के लिए मेरा पसंदीदा तरीका होगा जो समर्थन करते हैं remove()


उफ़, क्षमा करें ... यह निहित है कि मैं पुनरावृत्ति हटाने की विधि का उपयोग करूँगा, न कि कंटेनर का। और कितना ओवरहेड सूची बनाने की नकल करता है? यह ज्यादा नहीं हो सकता है और चूंकि यह एक विधि के लिए तैयार है, इसलिए इसे जल्दी से इकट्ठा किया जाना चाहिए। संपादन देखें ..
user1329572

1
@ मुझे लगता है कि यह Iteratorइंटरफ़ेस के हटाने के तरीके को जावदोस्क में वैकल्पिक के रूप में चिह्नित करने के लायक है, जिसका अर्थ है कि वहाँ Iterator कार्यान्वयन हो सकता है जो फेंक सकता है UnsupportedOperationException। जैसे, मैं कहूँगा कि यह दृष्टिकोण पहले वाले की तुलना में कम सुरक्षित है। उपयोग किए जाने वाले इरादों के आधार पर, पहला दृष्टिकोण अधिक उपयुक्त हो सकता है।
एडविन डेलोरजो

remove()मूल संग्रह पर @EdwinDalorzo खुद भी फेंक सकते हैं UnsupportedOperationException: docs.oracle.com/javase/7/docs/api/java/util/… । जावा कंटेनर इंटरफेस, दुख की बात है, अत्यंत अविश्वसनीय (इंटरफ़ेस के बिंदु को ईमानदारी से हराते हुए) परिभाषित किया गया है। यदि आप सटीक कार्यान्वयन को नहीं जानते हैं जो रनटाइम में उपयोग किया जाएगा, तो यह अपरिवर्तनीय तरीके से करना बेहतर है - उदाहरण के लिए, तत्वों को छानने और उन्हें एक नए कंटेनर में इकट्ठा करने के लिए जावा 8+ स्ट्रीम एपीआई का उपयोग करें, फिर पूरी तरह से पुराने को इसके साथ बदलें।
मैथ्यू 19

5

केवल दूसरा दृष्टिकोण काम करेगा। आप iterator.remove()केवल उपयोग करके पुनरावृत्ति के दौरान संग्रह को संशोधित कर सकते हैं । अन्य सभी प्रयास कारण बनेंगे ConcurrentModificationException


2
पहला प्रयास एक प्रतिलिपि पर प्रसारित होता है, जिसका अर्थ है कि वह मूल को संशोधित कर सकता है।
कॉलिन डी

3

पुराने पसंदीदा (यह अभी भी काम करता है):

List<String> list;

for(int i = list.size() - 1; i >= 0; --i) 
{
        if(list.get(i).contains("bad"))
        {
                list.remove(i);
        }
}

1

आप दूसरा नहीं कर सकते, क्योंकि अगर आप Iteratorremove() पर विधि का उपयोग करते हैं , तो भी आपको एक अपवाद मिलेगा

व्यक्तिगत रूप से, मैं सभी Collectionउदाहरणों के लिए पहले को पसंद करूंगा , नया बनाने के अतिरिक्त ओवरहेड के बावजूद Collection, मुझे अन्य डेवलपर्स द्वारा संपादित करने के दौरान त्रुटि की संभावना कम है। कुछ संग्रह कार्यान्वयन पर, Iterator remove()समर्थित है, अन्य पर यह नहीं है। आप Iterator के लिए डॉक्स में अधिक पढ़ सकते हैं ।

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


0

मैं दूसरा चुनूंगा क्योंकि आपको मेमोरी की कॉपी नहीं करनी है और Iterator तेजी से काम करता है। तो आप स्मृति और समय बचाते हैं।


" Iterator तेजी से काम करता है "। इस दावे का समर्थन करने के लिए कुछ भी? इसके अलावा, किसी सूची की प्रतिलिपि बनाने का मेमोरी पदचिह्न बहुत तुच्छ है, खासकर क्योंकि यह एक विधि के भीतर स्कोप किया जाएगा और लगभग तुरंत एकत्र किया जाएगा।
user1329572

1
पहले दृष्टिकोण में नुकसान यह है कि हमें दो बार पुनरावृत्त करना पड़ता है। हम एक तत्व की तलाश में फूर-लूप में पुनरावृत्ति करते हैं, और एक बार जब हम इसे ढूंढ लेते हैं, तो हम इसे मूल सूची से हटाने के लिए कहते हैं, जो इस दिए गए आइटम को देखने के लिए दूसरा पुनरावृत्ति कार्य होगा। यह दावे का समर्थन करेगा कि, कम से कम इस मामले में, पुनरावृत्ति दृष्टिकोण अधिक तेज़ होना चाहिए। हमें यह विचार करना होगा कि संग्रह का केवल संरचनात्मक स्थान ही बनाया जा रहा है, संग्रह के अंदर की वस्तुओं को कॉपी नहीं किया जा रहा है। दोनों संग्रह एक ही वस्तुओं का संदर्भ रखेंगे। जब GC होता है तो हम नहीं बता सकते हैं !!!
एडविन डेलोरजो

-2

ऐसा क्यों नहीं?

for( int i = 0; i < Foo.size(); i++ )
{
   if( Foo.get(i).equals( some test ) )
   {
      Foo.remove(i);
   }
}

और अगर यह एक नक्शा है, तो सूची नहीं, आप कीसेट का उपयोग कर सकते हैं ()


4
इस दृष्टिकोण के कई बड़े नुकसान हैं। सबसे पहले, हर बार जब आप किसी तत्व को निकालते हैं, तो अनुक्रमणिका को पुनर्गठित किया जाता है। इसलिए, यदि आप तत्व 0 को हटाते हैं, तो तत्व 1 नया तत्व बन जाता है 0. यदि आप इस पर जा रहे हैं, तो कम से कम इस समस्या से बचने के लिए पीछे की ओर करें। दूसरा, सभी सूची कार्यान्वयन तत्वों तक सीधी पहुँच प्रदान नहीं करते हैं (जैसा कि एरियरिस्ट करता है)। एक लिंक्डलिस्ट में यह जबरदस्त रूप से अक्षम होगा क्योंकि हर बार जब आप जारी करते get(i)हैं तो आपको सभी नोड्स तक यात्रा करनी होती है जब तक आप नहीं पहुंचते i
एडविन डेलोरजो

इस पर कभी विचार नहीं किया क्योंकि मैं आमतौर पर इसका उपयोग केवल एक आइटम को निकालने के लिए करता था जिसे मैं खोज रहा था। जानकार अच्छा लगा।
ड्रेक क्लेरिस

4
मुझे पार्टी में देर हो गई है, लेकिन निश्चित रूप से अगर Foo.remove(i);आपको करना चाहिए तो ब्लॉक कोड में i--;?
बर्टी वूवेन

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