পুনরাবৃত্তি করার সময় সংগ্রহ থেকে উপাদানগুলি সরান


215

আফাইক, দুটি পন্থা রয়েছে:

  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
শুধু কৌতূহলী, আপনি কেন প্রথম উদাহরণে বোকা লোকদের দ্বারা লুপিং না করে বোকা লোকগুলির অনুলিপি তৈরি করেন?
হাজ

@ হ্যাজ, সুতরাং আমাকে কেবল একবার লুপ করতে হবে।
ব্যবহারকারী 1329572

15
নোট: 'কিন্তু' এছাড়াও iterators সঙ্গে পরিবর্তনশীল করার সুযোগ সীমিত করতে বেশি 'এর জন্য' পছন্দ করা: এর জন্য (ইটারেটর <ফু> আইটিআর = fooList.iterator (); itr.hasNext ();) {}
তাম্রাভ

আমি জানি না এর 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পুনরাবৃত্তির সময় আপনি নতুন পদ্ধতি যুক্ত করার জন্যও এর পদ্ধতিটি ব্যবহার করতে পারেন ।

জেডিকে> = 8 ব্যবহার করা

জাভা 8 বা ততোধিক সংস্করণগুলির সাথে যারা কাজ করছেন তাদের জন্য, এমন কয়েকটি কৌশল রয়েছে যা আপনি এর সুবিধা নিতে ব্যবহার করতে পারেন।

আপনি বেস শ্রেণিতে নতুন removeIfপদ্ধতিটি ব্যবহার করতে পারেন Collection:

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

অথবা নতুন স্ট্রিম এপিআই ব্যবহার করুন:

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ইন্টারফেসের অপসারণের পদ্ধতিটি জাভাদোকসে "alচ্ছিক" হিসাবে চিহ্নিত হয়েছে, যার অর্থ এমন Iteratorযে বাস্তবায়ন হতে পারে যা UnsupportedOperationExceptionআমরা অপসারণের পদ্ধতিটি অনুরোধ করলে নিক্ষেপ করতে পারে । এই হিসাবে, আমি বলব যে এই উপাদানগুলি অপসারণের জন্য পুনরাবৃত্তি সমর্থনের গ্যারান্টি দিতে না পারলে অন্যদের চেয়ে এই পদ্ধতিটি কম নিরাপদ।

বলিহারি! এটিই সুনিশ্চিত গাইড।
ম্যাগনো সি

এটি একটি নিখুঁত উত্তর! ধন্যবাদ.
উইলহেল্ম

7
জেডি কে 8 স্ট্রিম সম্পর্কে আপনার অনুচ্ছেদে আপনি উল্লেখ করেছেন removeAll(filtered)। এর জন্য একটি শর্টকাট হবেremoveIf(b -> b.getIsbn().equals(other))
ifloop

Iterator এবং listIterator মধ্যে পার্থক্য কি?
আলেকজান্ডার মিলস

অপসারণ বিবেচনা করা হয়নি, তবে এটি আমার প্রার্থনার উত্তর ছিল। ধন্যবাদ!
আকাবেলে

15

জাভা 8-তে আরও একটি পদ্ধতি রয়েছে। সংগ্রহ # removeIf

উদাহরণ:

List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);

list.removeIf(i -> i > 2);

13

অন্যটির চেয়ে একটি পদ্ধতির পছন্দ করার কোনও কারণ আছে কি?

প্রথম পদ্ধতির কাজ করবে, তবে তালিকাটি অনুলিপি করার সুস্পষ্ট ওভারহেড রয়েছে।

দ্বিতীয় পন্থাটি কাজ করবে না কারণ অনেকগুলি ধারক পুনরাবৃত্তির সময় পরিবর্তনের অনুমতি দেয় না। এর মধ্যে রয়েছেArrayList

শুধুমাত্র পরিমার্জন বর্তমান উপাদান মুছে ফেলার জন্য হয়, তাহলে আপনি ব্যবহার করে দ্বিতীয় পদ্ধতির কাজ করতে পারে itr.remove()(যে, ব্যবহার পুনরুক্তিকারীর এর remove()পদ্ধতি, না ধারক 'গুলি)। সমর্থনকারী পুনরাবৃত্তির জন্য এটি আমার পছন্দসই পদ্ধতি হবে remove()


ওফস, দুঃখিত ... এটি বোঝানো হয়েছে যে আমি পুনরাবৃত্তকারীকে অপসারণের পদ্ধতিটি ব্যবহার করব, ধারকটি নয়। এবং কতটা ওভারহেড তালিকাটি অনুলিপি তৈরি করে? এটি খুব বেশি হতে পারে না এবং যেহেতু এটি কোনও পদ্ধতিতে বিস্তৃত হয় তাই এটি দ্রুত আবর্জনা সংগ্রহ করা উচিত। সম্পাদনা দেখুন ..
ব্যবহারকারী 1329572

1
@aix আমার মনে হয় এটা উল্লেখ অপসারণ পদ্ধতি সাধ্যমতো Iteratorইন্টারফেস Javadocs ঐচ্ছিক, যার মানে হিসাবে চিহ্নিত আছে সেখানে ইটারেটর বাস্তবায়নের যে নিক্ষেপ করা হতে পারে হতে পারে যে UnsupportedOperationException। এই হিসাবে, আমি বলব যে এই পদ্ধতিরটি প্রথমটির চেয়ে কম নিরাপদ। ব্যবহারের উদ্দেশ্যে করা বাস্তবায়নগুলির উপর নির্ভর করে, প্রথম পদ্ধতির আরও উপযুক্ত হতে পারে।
এডউইন ডালোরজো

@EdwinDalorzo remove()মূল সংগ্রহ নিজেই এছাড়াও নিক্ষেপ করা হতে পারে UnsupportedOperationException: docs.oracle.com/javase/7/docs/api/java/util/... । জাভা ধারক ইন্টারফেসগুলি অত্যন্ত দুঃখজনকভাবে সংজ্ঞায়িত করা হয়েছে যে এটি অত্যন্ত বিশ্বাসযোগ্য নয় (ইন্টারফেসের বিন্দুকে পরাস্ত করে, সততার সাথে)। রানটাইমের সময় ব্যবহার করা হবে এমন সঠিক প্রয়োগটি যদি আপনি না জানেন তবে অপরিবর্তনীয় উপায়ে কাজ করা ভাল - উদাহরণস্বরূপ, উপাদানগুলিকে নিচে ফিল্টার করতে এবং নতুন পাত্রে সংগ্রহ করার জন্য জাভা 8+ স্ট্রিমস এপিআই ব্যবহার করুন, তারপরে পুরানোটিকে সম্পূর্ণরূপে এটির সাথে প্রতিস্থাপন করুন।
ম্যাথু পড়ুন

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

কারণ এমনকি যদি আপনি ব্যবহার দ্বিতীয় করতে পারবেন না, remove()উপর পদ্ধতি ইটারেটর , আপনি একটি ব্যতিক্রম নিক্ষিপ্ত পাবেন

ব্যক্তিগতভাবে, আমি সমস্ত Collectionউদাহরণের জন্য প্রথমটিকে প্রাধান্য দেব , নতুনটি তৈরির অতিরিক্ত শোনার পরেও Collection, অন্যান্য বিকাশকারীদের সম্পাদনার সময় আমি ত্রুটির প্রবণতা কম বলে মনে করি। কিছু সংগ্রহ বাস্তবায়নে, আইট্রেটর remove()সমর্থিত হয়, অন্যদিকে তা হয় না। আপনি Iterator এর জন্য ডক্সে আরও পড়তে পারেন ।

তৃতীয় বিকল্পটি হ'ল একটি নতুন তৈরি করা Collection, আসলটির উপরে পুনরাবৃত্তি করা এবং প্রথমটির সমস্ত সদস্যকে Collectionদ্বিতীয়টিতে যুক্ত করা Collectionযা মুছে ফেলার জন্য প্রস্তুত নয় । আকার Collectionএবং মোছার সংখ্যার উপর নির্ভর করে প্রথম পদ্ধতির তুলনায় এটি মেমরির উল্লেখযোগ্যভাবে সংরক্ষণ করতে পারে।


0

আমি দ্বিতীয়টি বেছে নেব কারণ আপনাকে মেমরির একটি অনুলিপি করতে হবে না এবং Iterator দ্রুত কাজ করে। সুতরাং আপনি স্মৃতি এবং সময় সংরক্ষণ করুন।


" Iterator দ্রুত কাজ করে "। এই দাবি সমর্থন কিছু? এছাড়াও, একটি তালিকার একটি অনুলিপি তৈরির স্মৃতি পদক্ষেপ খুব তুচ্ছ, বিশেষত যেহেতু এটি কোনও পদ্ধতির মধ্যে পড়ে এবং আবর্জনা প্রায় অবিলম্বে সংগ্রহ করা হবে।
ব্যবহারকারী 1329572

1
প্রথম পদ্ধতির অসুবিধাটি হ'ল আমাদের দু'বার পুনরাবৃত্তি করতে হবে। আমরা কোনও উপাদানটির সন্ধানে দুষ্টু লুপটিতে পুনরাবৃত্তি করি এবং এটি সন্ধান করার পরে আমরা এটিকে মূল তালিকা থেকে সরাতে বলি, যা এই প্রদত্ত আইটেমটি অনুসন্ধান করার জন্য দ্বিতীয় পুনরাবৃত্তির কাজকে বোঝায়। এটি এই দাবিকে সমর্থন করবে যে, অন্তত এই ক্ষেত্রে, পুনরাবৃত্তির পদ্ধতির দ্রুত হওয়া উচিত। আমাদের বিবেচনা করতে হবে যে সংগ্রহের কেবল কাঠামোগত স্থানই তৈরি হচ্ছে, সংগ্রহের অভ্যন্তরীণ বস্তুগুলি অনুলিপি করা হচ্ছে না। উভয় সংগ্রহ একই জিনিসগুলির রেফারেন্স রাখবে। জিসি যখন হয় তখন আমরা বলতে পারি না !!!
এডউইন ডালোরজো

-2

কেন এই না?

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

এবং যদি এটি মানচিত্র নয়, তালিকা নয়, আপনি কীসেট ব্যবহার করতে পারেন ()


4
এই পদ্ধতির অনেক বড় অসুবিধা রয়েছে। প্রথমত, আপনি যখনই কোনও উপাদান অপসারণ করেন, সূচিগুলি পুনরায় সাজানো হয়। অতএব, আপনি যদি উপাদান 0 অপসারণ করেন, তবে উপাদান 1টি নতুন উপাদান 0 হয়ে যায় you আপনি যদি এটির দিকে চলে যান তবে কমপক্ষে এই সমস্যাটি এড়াতে পিছনের দিকে করুন। দ্বিতীয়ত, সমস্ত তালিকা প্রয়োগকারী উপাদানগুলিতে সরাসরি অ্যাক্সেস সরবরাহ করে না (অ্যারেলিস্ট যেমন করে)। লিঙ্কডলিস্টে এটি মারাত্মকভাবে অদক্ষ হয়ে উঠবে কারণ প্রতিবার আপনি যখন ইস্যু করেন তখন get(i)পৌঁছানো পর্যন্ত আপনাকে সমস্ত নোড দেখতে হবে i
এডউইন ডালোরজো

এটিকে কখনই বিবেচনা করবেন না কারণ আমি সাধারণত এটি সন্ধান করেছি এমন একটি আইটেম সরানোর জন্য ব্যবহার করেছি। জানা ভাল.
ড্রার ক্লারিস

4
আমি পার্টিতে দেরি করছি, তবে অবশ্যই যদি ব্লক কোডে Foo.remove(i);আপনার করা উচিত i--;?
বার্টি হুইন

এটি কমে গেছে
জ্যাক
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.