পুনরুক্তির সময় উপাদানগুলি std :: সেট থেকে মোছা হচ্ছে


147

আমাকে একটি সেট দিয়ে যেতে হবে এবং এমন উপাদানগুলি সরিয়ে ফেলতে হবে যা পূর্বনির্ধারিত মানদণ্ডের সাথে মিলিত হয়।

এটি আমি লিখেছি পরীক্ষার কোড:

#include <set>
#include <algorithm>

void printElement(int value) {
    std::cout << value << " ";
}

int main() {
    int initNum[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
    std::set<int> numbers(initNum, initNum + 10);
    // print '0 1 2 3 4 5 6 7 8 9'
    std::for_each(numbers.begin(), numbers.end(), printElement);

    std::set<int>::iterator it = numbers.begin();

    // iterate through the set and erase all even numbers
    for (; it != numbers.end(); ++it) {
        int n = *it;
        if (n % 2 == 0) {
            // wouldn't invalidate the iterator?
            numbers.erase(it);
        }
    }

    // print '1 3 5 7 9'
    std::for_each(numbers.begin(), numbers.end(), printElement);

    return 0;
}

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

আমার প্রশ্ন: এটি কি এসটিডি সেটগুলির জন্য নির্ধারিত আচরণ বা এই বাস্তবায়ন নির্দিষ্ট? আমি যাইহোক, উবুন্টু 10.04 (32-বিট সংস্করণ) -এ জিসিসি 4.3.3 ব্যবহার করছি।

ধন্যবাদ!

প্রস্তাবিত সমাধান:

সেট থেকে উপাদানগুলি পুনরাবৃত্তি এবং মুছতে এটি কি সঠিক উপায়?

while(it != numbers.end()) {
    int n = *it;
    if (n % 2 == 0) {
        // post-increment operator returns a copy, then increment
        numbers.erase(it++);
    } else {
        // pre-increment operator increments, then return
        ++it;
    }
}

সম্পাদনা করুন: সমাধান সমাধান

আমি এমন একটি সমাধান পেয়েছিলাম যা আমার কাছে আরও মার্জিত বলে মনে হয়, যদিও এটি ঠিক একই রকম হয়।

while(it != numbers.end()) {
    // copy the current iterator then increment it
    std::set<int>::iterator current = it++;
    int n = *current;
    if (n % 2 == 0) {
        // don't invalidate iterator it, because it is already
        // pointing to the next element
        numbers.erase(current);
    }
}

যদি সময়ের মধ্যে বেশ কয়েকটি পরীক্ষার শর্ত থাকে তবে তাদের প্রত্যেককে অবশ্যই পুনরাবৃত্তিকে বৃদ্ধি করতে হবে। আমি এই কোডটি আরও ভাল পছন্দ করি কারণ কোডটি কম ত্রুটি-প্রবণ এবং আরও পঠনযোগ্য করে এটারেটরটি কেবলমাত্র এক জায়গায় বাড়ানো হয়।


1
জিজ্ঞাসা করা হয়েছে এবং উত্তর দেওয়া হয়েছে: স্ট্যাকওভারফ্লো.com
মার্টিন ইয়র্ক

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

1
@pedromanoel এর ++itচেয়ে কিছুটা বেশি দক্ষ হওয়া উচিত it++কারণ এটির পুনরুক্তির কোনও অদৃশ্য অস্থায়ী অনুলিপি ব্যবহারের প্রয়োজন হয় না। কার্নেলের সংস্করণ দীর্ঘকাল পর্যন্ত নিশ্চিত করে যে ফিল্টারবিহীন উপাদানগুলিকে সবচেয়ে দক্ষতার সাথে পুনরাবৃত্তি করা হয়।
Alnitak

@ অ্যালনাইটাক আমি সে সম্পর্কে ভাবিনি, তবে আমি মনে করি যে পারফরম্যান্সের পার্থক্য এত বড় হবে না। অনুলিপিটিও তার সংস্করণে তৈরি হয়েছে, তবে কেবল সেই উপাদানগুলির জন্য যা মেলে। সুতরাং অপ্টিমাইজেশনের ডিগ্রি পুরোপুরি সেটটির কাঠামোর উপর নির্ভরশীল। বেশ কিছু সময়ের জন্য আমি কোডটি প্রাক-অনুকূলিতকরণ করেছি, প্রক্রিয়ায় পাঠযোগ্যতা এবং কোডিংয়ের গতিতে আঘাত করছি ... সুতরাং আমি অন্য উপায়ে ব্যবহার করার আগে কিছু পরীক্ষা করবো।
পেড্রোম্যানোয়েল

উত্তর:


178

এটি বাস্তবায়ন নির্ভর:

স্ট্যান্ডার্ড 23.1.2.8:

সন্নিবেশকৃত সদস্যগণ পুনরাবৃত্তকারীগুলির বৈধতা এবং ধারকটির রেফারেন্সকে প্রভাবিত করবে না এবং মুছে ফেলা সদস্যরা কেবল মুছে ফেলা উপাদানগুলির পুনরাবৃত্তি এবং রেফারেন্সকে অকার্যকর করবে।

হতে পারে আপনি এটি চেষ্টা করে দেখতে পারেন - এটি স্ট্যান্ডার্ড অনুসারে:

for (auto it = numbers.begin(); it != numbers.end(); ) {
    if (*it % 2 == 0) {
        numbers.erase(it++);
    }
    else {
        ++it;
    }
}

মনে রাখবেন যে এটি ++ পোস্টফিক্স, অতএব এটি মুছতে পুরানো অবস্থানটি পাস করে তবে অপারেটরের কারণে প্রথমে নতুনটিতে যায়।

2015.10.27 আপডেট: সি ++ 11 ত্রুটিটি সমাধান করেছে। iterator erase (const_iterator position);সর্বশেষ উপাদানটিকে মুছে ফেলা উপাদানটিকে পুনরাবৃত্তি করুন (বা set::end, যদি শেষ উপাদানটি সরানো থাকে)। সুতরাং সি ++ 11 শৈলীটি হ'ল:

for (auto it = numbers.begin(); it != numbers.end(); ) {
    if (*it % 2 == 0) {
        it = numbers.erase(it);
    }
    else {
        ++it;
    }
}

2
এটি deque এমএসভিসি ২০১৩ এ কাজ করে না । হয় তাদের বাস্তবায়ন বগি বা অন্য কোনও প্রয়োজনীয়তা রয়েছে যা এটিকে কাজ করতে বাধা দেয় deque। এসটিএল স্পেকটি এতটাই সংশ্লেষিত যে আপনি সমস্ত বাস্তবায়ন এটি অনুসরণ করতে পারে না আশা করতে পারেন, আপনার নৈমিত্তিক প্রোগ্রামার এটি মুখস্ত করতে দিন। এসটিএল হ'ল টেম্পিংয়ের বাইরে একটি দানব, এবং যেহেতু কোনও অনন্য বাস্তবায়ন নেই (এবং টেস্ট স্যুটগুলি, যদি কোনও স্পষ্টতই কোনও লুপের উপাদানগুলি মোছার মতো স্পষ্ট কেসগুলি আবরণ করে না), এটি এসটিএলকে একটি চকচকে ভঙ্গুর খেলনা করে তোলে যা এর সাথে যেতে পারে can আপনি যখন পাশের দিকে তাকান তখন একটি ব্যাং
কুরোই নেখো

@MatthieuM। এটি সি ++ 11 এ করে। সি ++ 17 এ এখন পুনরুক্তি লাগে (সি ++ 11 এ কনস্টেটাইটারেটর)।
tartaruga_casco_mole

19

আপনি যদি ভ্যালগ্রিন্ডের মাধ্যমে আপনার প্রোগ্রামটি চালনা করেন তবে আপনি পড়ার ত্রুটিগুলির একটি গোছা দেখতে পাবেন। অন্য কথায়, হ্যাঁ, পুনরাবৃত্তিকারীদের অবৈধ করা হচ্ছে, তবে আপনি নিজের উদাহরণে ভাগ্যবান হয়ে উঠছেন (বা সত্যই দুর্ভাগ্য, কারণ আপনি অপরিজ্ঞাত আচরণের নেতিবাচক প্রভাব দেখছেন না)। এর একটি সমাধান হ'ল অস্থায়ী পুনরাবৃত্তকারী তৈরি করা, অস্থায়ী বৃদ্ধি করা, লক্ষ্য পুনরাবৃত্তিকে মুছুন, তারপরে অস্থির উদ্দেশ্যে লক্ষ্য নির্ধারণ করুন। উদাহরণস্বরূপ, নীচে আপনার লুপটি আবার লিখুন:

std::set<int>::iterator it = numbers.begin();                               
std::set<int>::iterator tmp;                                                

// iterate through the set and erase all even numbers                       
for ( ; it != numbers.end(); )                                              
{                                                                           
    int n = *it;                                                            
    if (n % 2 == 0)                                                         
    {                                                                       
        tmp = it;                                                           
        ++tmp;                                                              
        numbers.erase(it);                                                  
        it = tmp;                                                           
    }                                                                       
    else                                                                    
    {                                                                       
        ++it;                                                               
    }                                                                       
} 

যদি এটি শুধুমাত্র শর্ত হয়ে থাকে যে ক্ষেত্রে এবং স্কোপ ইনিশিয়েশন বা পোস্ট-অপারেশন প্রয়োজন হয় না তবে whileলুপটি ব্যবহার করা আরও ভাল । অর্থাত্for ( ; it != numbers.end(); )while (it != numbers.end())
iammilind

7

"অপরিজ্ঞাত আচরণ" এর অর্থ আপনি ভুল বুঝেছেন। অপরিজ্ঞাত আচরণের অর্থ এই নয় যে "আপনি যদি এটি করেন তবে আপনার প্রোগ্রামটি ক্রাশ হবে বা অপ্রত্যাশিত ফলাফল আনবে ।" এর অর্থ "আপনি যদি এটি করেন তবে আপনার প্রোগ্রামটি ক্র্যাশ করতে পারে বা অপ্রত্যাশিত ফলাফল আনতে পারে", বা আপনার সংকলক, আপনার অপারেটিং সিস্টেম, চাঁদের পর্ব ইত্যাদির উপর নির্ভর করে অন্য কিছু করতে পারে or

যদি কোনও কিছু ক্র্যাশ না করে কার্যকর হয় এবং আপনি যেমনটি প্রত্যাশা করেন তেমন আচরণ করে, তবে এটি প্রমাণ নয় যে এটি অপরিবর্তিত আচরণ নয়। এগুলি প্রমাণ করে যে এটির আচরণটি সেই নির্দিষ্ট অপারেটিং সিস্টেমে comp নির্দিষ্ট সংকলকের সাথে সংকলনের পরে সেই নির্দিষ্ট রানের জন্য যেমন পর্যবেক্ষণ করা হয়েছিল।

একটি সেট থেকে একটি উপাদান মুছে ফেলা মুছে ফেলা উপাদান পুনরাবৃত্তি অকার্যকর। একটি অবৈধ পুনরাবৃত্তকারী ব্যবহার অনির্ধারিত আচরণ। এটি ঠিক তাই ঘটেছে যে পর্যবেক্ষণ করা আচরণটি আপনি এই বিশেষ পরিস্থিতিতে যা ইচ্ছা করেছিলেন; এর অর্থ এই নয় যে কোডটি সঠিক।


ওহ, আমি ভালভাবে অবগত যে অপরিজ্ঞাত আচরণের অর্থ "এটি আমার পক্ষে কাজ করে তবে সবার জন্য নয়" can এই কারণেই আমি এই প্রশ্নটি জিজ্ঞাসা করেছি, কারণ আমি জানতাম না যে এই আচরণটি সঠিক কিনা। যদি এটি হত তবে আমি ঠিক সেভাবেই চলে যাব। কিছুক্ষণ লুপ ব্যবহার করা আমার সমস্যার সমাধান করবে, তাই না? আমি আমার প্রস্তাবিত সমাধান দিয়ে আমার প্রশ্নটি সম্পাদনা করেছি। অনুগ্রহপূর্বক এটি ভালো করে দেখুন.
পেড্রোমোনেল

এটি আমার পক্ষেও কাজ করে। তবে আমি যখন শর্তটি এর মধ্যে পরিবর্তন করি if (n > 2 && n < 7 )তখন আমি 0 1 2 4 7 8 9. পাই - এখানে নির্দিষ্ট ফলাফল সম্ভবত মুছে ফেলার পদ্ধতিটি এবং পুনরুদ্ধারের পুনরুদ্ধারের পরিবর্তে মুছে ফেলার পদ্ধতিটির উপর নির্ভর করে (এটি নয়) প্রয়োগের বিশদগুলির উপর কখনই নির্ভর করা উচিত)। ;)
আঙ্কেলবেন্স

1
এসটিএল "অপরিবর্তিত আচরণ" -এ প্রচুর নতুন অর্থ যুক্ত করে। উদাহরণস্বরূপ "মাইক্রোসফ্ট std::set::eraseএকটি পুনরাবৃত্তকারীকে ফেরত দেওয়ার অনুমতি দিয়ে অনুমানটিকে বাড়িয়ে তোলার জন্য স্মার্ট বলে মনে করেছিল , সুতরাং আপনার এমএসভিসি কোডটি জিসিসি দ্বারা সংকলন std::bitset::operator[]করার সময় একটি ধাক্কা সহকারে উঠে যাবে ", বা "মাইক্রোসফ্ট আপনার সাবধানতার সাথে অপ্টিমাইজড বিটসেট অ্যালগরিদমকে ধীর করে দেবে এমএসভিসির সাথে সংকলিত হয়ে ক্রল করুন "। এসটিএলটির কোনও অনন্য বাস্তবায়ন নেই এবং এর স্পিকটি একটি তাত্পর্যপূর্ণভাবে ক্রমবর্ধমান পুষ্পিত জঞ্জাল, তাই কোনও লুপের ভিতর থেকে উপাদানগুলি মুছে ফেলার জন্য সিনিয়র প্রোগ্রামার দক্ষতার প্রয়োজন নেই ...
কুড়োই নেখো

2

কেবল সতর্ক করার জন্য, যে কোনও ডেক পাত্রে ক্ষেত্রে, সমস্ত সংখ্যার সমাধান যা ডেক্ট ইটারেটর সমতার জন্য সংখ্যাগুলিতে সমাপ্তি পরীক্ষা করে end যথা, ডেকের একটি উপাদান মুছে ফেলা সাধারণত পয়েন্টারকে সংখ্যাগুলিতে অবৈধ করে দেয় end

#include <iostream>
#include <deque>

using namespace std;
int main() 
{

  deque<int> numbers;

  numbers.push_back(0);
  numbers.push_back(1);
  numbers.push_back(2);
  numbers.push_back(3);
  //numbers.push_back(4);

  deque<int>::iterator  it_end = numbers.end();

  for (deque<int>::iterator it = numbers.begin(); it != numbers.end(); ) {
    if (*it % 2 == 0) {
      cout << "Erasing element: " << *it << "\n";
      numbers.erase(it++);
      if (it_end == numbers.end()) {
    cout << "it_end is still pointing to numbers.end()\n";
      } else {
    cout << "it_end is not anymore pointing to numbers.end()\n";
      }
    }
    else {
      cout << "Skipping element: " << *it << "\n";
      ++it;
    }
  }
}

আউটপুট:

Erasing element: 0
it_end is still pointing to numbers.end()
Skipping element: 1
Erasing element: 2
it_end is not anymore pointing to numbers.end()

মনে রাখবেন যে এই নির্দিষ্ট ক্ষেত্রে ডিক ট্রান্সফর্মেশনটি সঠিক হলেও শেষ পয়েন্টারটি সেই পথে অবৈধ করা হয়েছে। একটি ভিন্ন আকারের deque সঙ্গে ত্রুটি আরও প্রকট:

int main() 
{

  deque<int> numbers;

  numbers.push_back(0);
  numbers.push_back(1);
  numbers.push_back(2);
  numbers.push_back(3);
  numbers.push_back(4);

  deque<int>::iterator  it_end = numbers.end();

  for (deque<int>::iterator it = numbers.begin(); it != numbers.end(); ) {
    if (*it % 2 == 0) {
      cout << "Erasing element: " << *it << "\n";
      numbers.erase(it++);
      if (it_end == numbers.end()) {
    cout << "it_end is still pointing to numbers.end()\n";
      } else {
    cout << "it_end is not anymore pointing to numbers.end()\n";
      }
    }
    else {
      cout << "Skipping element: " << *it << "\n";
      ++it;
    }
  }
}

আউটপুট:

Erasing element: 0
it_end is still pointing to numbers.end()
Skipping element: 1
Erasing element: 2
it_end is still pointing to numbers.end()
Skipping element: 3
Erasing element: 4
it_end is not anymore pointing to numbers.end()
Erasing element: 0
it_end is not anymore pointing to numbers.end()
Erasing element: 0
it_end is not anymore pointing to numbers.end()
...
Segmentation fault (core dumped)

এটি ঠিক করার একটি উপায় এখানে:

#include <iostream>
#include <deque>

using namespace std;
int main() 
{

  deque<int> numbers;
  bool done_iterating = false;

  numbers.push_back(0);
  numbers.push_back(1);
  numbers.push_back(2);
  numbers.push_back(3);
  numbers.push_back(4);

  if (!numbers.empty()) {
    deque<int>::iterator it = numbers.begin();
    while (!done_iterating) {
      if (it + 1 == numbers.end()) {
    done_iterating = true;
      } 
      if (*it % 2 == 0) {
    cout << "Erasing element: " << *it << "\n";
      numbers.erase(it++);
      }
      else {
    cout << "Skipping element: " << *it << "\n";
    ++it;
      }
    }
  }
}

মূল সত্তা do not trust an old remembered dq.end() value, always compare to a new call to dq.end()
জেসি চিশল্ম

2

সি ++ ২০ এর "ইউনিফর্ম ধারক মুছা" থাকবে এবং আপনি লিখতে সক্ষম হবেন:

std::erase_if(numbers, [](int n){ return n % 2 == 0 });

এবং যে জন্য কাজ করবে vector, set, deque, ইত্যাদি দেখুন cppReference আরও তথ্যের জন্য।


1

এই আচরণ বাস্তবায়ন নির্দিষ্ট। পুনরাবৃত্তির সঠিকতার গ্যারান্টি দেওয়ার জন্য আপনার "এটি = সংখ্যা.এরসে (এটি) ব্যবহার করা উচিত;" বিবৃতি যদি আপনার প্রয়োজন হয় উপাদান মুছতে এবং অন্য ক্ষেত্রে সহজভাবে পুনরাবৃত্তি।


1
Set<T>::eraseসংস্করণ পুনরুক্তি ফেরায় না
আরকাইৎজ জিমেনেজ 20'10

4
আসলে এটি করে তবে কেবল এমএসভিসি বাস্তবায়নে। সুতরাং এটি সত্যই একটি বাস্তবায়ন নির্দিষ্ট উত্তর। :)
ইউজিন

1
@ ইউজিন এটি সি ++ 11
মাস্তভের

এর কিছু প্রয়োগের gcc 4.8সাথে c++1yমুছে ফেলার সমস্যা রয়েছে। it = collection.erase(it);কাজ করার কথা থাকলেও এটি ব্যবহার করা নিরাপদ হতে পারেcollection.erase(it++);
জেসি চিশলম

1

আমি মনে করি এসটিএল পদ্ধতি ' remove_if' এর থেকে পুনরুক্তিকারী দ্বারা মোড়ানো বস্তুটি মুছার চেষ্টা করার সময় কিছু অদ্ভুত সমস্যা রোধ করতে সহায়তা করতে পারে।

এই সমাধানটি কম দক্ষ হতে পারে।

ধরা যাক আমাদের কিছু ধরণের ধারক রয়েছে, যেমন ভেক্টর বা এম_ব্লেলেটস নামে একটি তালিকা:

Bullet::Ptr is a shared_pr<Bullet>

' it' সেই পুনরাবৃত্তি যা ' remove_if' প্রত্যাবর্তন করে, তৃতীয় আর্গুমেন্টটি একটি ল্যাম্বডা ফাংশন যা ধারকটির প্রতিটি উপাদানকেই কার্যকর করা হয়। Bullet::Ptrধারকটি রয়েছে বলে লাম্বদা ফাংশনটির জন্য সেই ধরণের (বা সেই ধরণের কোনও রেফারেন্স) আর্গুমেন্ট হিসাবে পাস হওয়া দরকার।

 auto it = std::remove_if(m_bullets.begin(), m_bullets.end(), [](Bullet::Ptr bullet){
    // dead bullets need to be removed from the container
    if (!bullet->isAlive()) {
        // lambda function returns true, thus this element is 'removed'
        return true;
    }
    else{
        // in the other case, that the bullet is still alive and we can do
        // stuff with it, like rendering and what not.
        bullet->render(); // while checking, we do render work at the same time
        // then we could either do another check or directly say that we don't
        // want the bullet to be removed.
        return false;
    }
});
// The interesting part is, that all of those objects were not really
// completely removed, as the space of the deleted objects does still 
// exist and needs to be removed if you do not want to manually fill it later 
// on with any other objects.
// erase dead bullets
m_bullets.erase(it, m_bullets.end());

' remove_if' ল্যাম্বদা ফাংশনটি সত্য হয়ে যায় এবং সেই সামগ্রীটি ধারকটির শুরুতে স্থানান্তরিত করে এমন পাত্রে সরিয়ে দেয়। '' it'একটি অপরিবর্তিত অবজেক্টের দিকে ইঙ্গিত করে যা আবর্জনা হিসাবে বিবেচনা করা যেতে পারে। 'এটি' থেকে এম_বুললেটস.এন্ড () পর্যন্ত অবজেক্টগুলি মুছে ফেলা যায়, কারণ তারা স্মৃতি ধারণ করে, তবে আবর্জনা ধারণ করে, সুতরাং 'মুছে ফেলুন' পদ্ধতিটি এই ব্যাপ্তিতে ডাকা হয়।


0

আমি একই পুরানো সমস্যা জুড়ে এসেছি এবং নীচের কোডটি আরও বোধগম্যরূপে পেয়েছি যা উপরের সমাধানগুলি অনুসারে একরকম।

std::set<int*>::iterator beginIt = listOfInts.begin();
while(beginIt != listOfInts.end())
{
    // Use your member
    std::cout<<(*beginIt)<<std::endl;

    // delete the object
    delete (*beginIt);

    // erase item from vector
    listOfInts.erase(beginIt );

    // re-calculate the begin
    beginIt = listOfInts.begin();
}

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