লুপের জন্য সি ++ 11 ব্যাপ্তিতে থাকা অবস্থায় ভেক্টর থেকে আইটেমটি সরানো হচ্ছে?


102

আমার কাছে IInventory * এর একটি ভেক্টর রয়েছে এবং আমি প্রতিটিের সাথে স্টাফ করার জন্য সি ++ 11 রেঞ্জ ব্যবহার করে তালিকার মধ্যে লুপ করছি।

একটি দিয়ে কিছু স্টাফ করার পরে, আমি এটি তালিকা থেকে মুছে ফেলতে এবং অবজেক্টটি মুছতে চাই। আমি জানি যে আমি deleteএটি পরিষ্কার করার জন্য যে কোনও সময় পয়েন্টারে কল করতে পারি , তবে পরিসর forলুপের মধ্যে থাকা অবস্থায় এটি ভেক্টর থেকে অপসারণ করার উপযুক্ত উপায় কী? এবং যদি আমি এটি তালিকা থেকে সরান তবে আমার লুপটি অবৈধ হবে?

std::vector<IInventory*> inv;
inv.push_back(new Foo());
inv.push_back(new Bar());

for (IInventory* index : inv)
{
    // Do some stuff
    // OK, I decided I need to remove this object from 'inv'...
}

8
যদি আপনি অভিনবতা পেতে চান, আপনি এমন std::remove_ifএকটি প্রাকটিক দিয়ে ব্যবহার করতে পারেন যা "স্টাফগুলি করে" এবং তারপরে আপনি যদি উপাদানটি সরিয়ে ফেলতে চান তবে সত্য ফিরে আসে returns
বেনজামিন লিন্ডলি

আপনি কেবল একটি সূচক কাউন্টার যুক্ত করতে না পারেন এবং তারপরে ইনভেরেস (সূচক) এর মতো কিছু ব্যবহার করতে পারবেন না এমন কোনও কারণ আছে কি?
টমজে

4
@ টমজে: এটি এখনও পুনরাবৃত্তি স্ক্রু করতে পারে।
বেন ভয়েগট

@ টমজে এবং এটি একটি পারফরম্যান্স কিলার - প্রতিটি মুছে ফেলার পরে, আপনি মুছে ফেলার পরে সমস্ত উপাদান সরিয়ে নিতে পারবেন।
ইভান

4
@ বেনভয়েট আমি std::listনীচে স্যুইচ করার পরামর্শ দিয়েছি
বোবোবোবো

উত্তর:


99

না, আপনি পারবেন না। আপনার ধারকটির forপ্রতিটি উপাদান একবারে অ্যাক্সেস করার প্রয়োজন হলে রেঞ্জ-ভিত্তিক ।

আপনার forপাশাপাশি চলার সাথে সাথে ধারকটি পরিবর্তন করতে হবে, একবারে একাধিক উপাদান অ্যাক্সেস করতে হবে বা অন্যথায় ধারকটির মাধ্যমে একটি লিনিয়ার ফ্যাশনে পুনরাবৃত্তি করতে হবে তবে আপনার স্বাভাবিক লুপ বা এর কাজিনের একটি ব্যবহার করা উচিত ।

উদাহরণ স্বরূপ:

auto i = std::begin(inv);

while (i != std::end(inv)) {
    // Do some stuff
    if (blah)
        i = inv.erase(i);
    else
        ++i;
}

5
এখানে প্রযোজ্য আইডিয়াম মুছে ফেলুন না?
নবীন

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

4
@ শেঠ কার্নেগি ইরেজ-রিমুভ একটি ল্যাম্বডা দিয়ে প্রেডিকেটটি মার্জিতভাবে অনুমতি দেয় (যেহেতু এটি ইতিমধ্যে সি ++ 11)
পোটোটোভেটর

11
এই সমাধানটি পছন্দ করবেন না, বেশিরভাগ পাত্রে এটি O (N ^ 2)। remove_ifভাল.
বেন ভয়েগট

6
এই উত্তরটি হল , সঠিক eraseনতুন বৈধ পুনরুক্তিকারীর ফেরৎ। এটি দক্ষ নাও হতে পারে তবে এটি কাজ করার গ্যারান্টিযুক্ত।
sp2danny

56

প্রতিবার কোনও উপাদান ভেক্টর থেকে সরানো হলে, আপনাকে অবশ্যই মুছে ফেলা উপাদানটির বা তার পরে পুনরাবৃত্তিকে ধরে নিতে হবে না, কারণ মুছে ফেলা উপাদানগুলির উত্তরাধিকারী প্রতিটি উপাদান সরানো হয়েছে।

লুপের জন্য একটি পরিসীমা ভিত্তিক কেবল পুনরাবৃত্তকারীদের ব্যবহার করে "স্বাভাবিক" লুপের জন্য সিনট্যাকটিক চিনি, সুতরাং উপরেরটি প্রযোজ্য।

যা বলা হচ্ছে, আপনি কেবল:

inv.erase(
    std::remove_if(
        inv.begin(),
        inv.end(),
        [](IInventory* element) -> bool {
            // Do "some stuff", then return true if element should be removed.
            return true;
        }
    ),
    inv.end()
);

6
" কারণ এমন একটি সম্ভাবনা রয়েছে যে ভেক্টর মেমরির ব্লকটিকে পুনরায় স্থান দেয় যাতে এটি তার উপাদানগুলিকে রাখে " না, vectorকল করার কারণে এটি কখনই পুনরায় বিলোপ করতে পারে না erase। পুনরাবৃত্তিকে অবৈধ করার কারণ হ'ল মুছে ফেলা উপাদানগুলির উত্তরাধিকারী প্রতিটি উপাদান সরানো হয়।
iljarn

4
একটি ক্যাপচার-ডিফল্ট [&]উপযুক্ত হবে, তাকে স্থানীয় ভেরিয়েবলগুলির সাথে "কিছু জিনিস করার" অনুমতি দেওয়ার জন্য।
পোটোটোওয়টার

4
এটি একটি পুনরুক্তিকারীর ভিত্তিক লুপ ছাড়া সহজ চেহারা না, উপরন্তু আপনি আপনার ঘিরা মনে রাখতে হবে remove_ifসঙ্গে .erase, অন্যথায় কিছুই ঘটবে।
বোবোবো

4
@ বোবোবোবু যদি "পুনরুত্পাদনকারী-ভিত্তিক লুপ" দ্বারা আপনি শেঠ কার্নেগির উত্তর বোঝাচ্ছেন , এটি গড়ে ও (এন ^ 2)। std::remove_ifও (এন) হয়।
ব্র্যাঙ্কো দিমিত্রিজেভিক

4
@ বোবোবো আপনি যদি না আসলে এলোমেলো অ্যাক্সেসের প্রয়োজন না থাকে।
ব্র্যাঙ্কো দিমিত্রিজেভিক

16

এটির পুনরাবৃত্তি করার সময় আপনার আদর্শভাবে ভেক্টরটি সংশোধন করা উচিত নয়। মুছে ফেলা মুছুন ব্যবহার করুন। যদি আপনি এটি করেন তবে আপনার কয়েকটি সমস্যা দেখা দেওয়ার সম্ভাবনা রয়েছে। একটি যেহেতু vectorএকটি eraseঅকার্যকর করে সব iterators উপাদান দিয়ে শুরু পর্যন্ত মুছে ফেলা হচ্ছে end()আপনি ব্যবহার দ্বারা আপনার iterators বৈধ নিশ্চিত করতে হবে:

for (MyVector::iterator b = v.begin(); b != v.end();) { 
    if (foo) {
       b = v.erase( b ); // reseat iterator to a valid value post-erase
    else {
       ++b;
    }
}

দ্রষ্টব্য, আপনার b != v.end()যেমন পরীক্ষাটি প্রয়োজন তেমন। আপনি যদি নীচে এটি অপ্টিমাইজ করার চেষ্টা করেন:

for (MyVector::iterator b = v.begin(), e = v.end(); b != e;)

আপনার eপ্রথম eraseকলের পরে অবৈধ হওয়ার কারণে আপনি ইউবিতে চলে যাবেন ।


@ ইল্ডজার্ন: হ্যাঁ, সত্য! এটি একটি টাইপ ছিল।
dirkgently

4
এটি মুছে ফেলুন-মুছে দেওয়ার আইডিয়ম নয়। এখানে কোনও কল নেই std::remove, এবং এটি ও (এন ^ 2) ও (এন) নয়।
পোটোটোওয়টার

4
@ পোটাটোসওয়াতর: অবশ্যই না। আপনার পুনরাবৃত্তি হওয়ার সাথে সাথে মুছে ফেলার সমস্যাগুলিকে আমি চিহ্নিত করার চেষ্টা করছিলাম। আমার অনুমান আমার শব্দটি মাস্টার পাস করেনি?
dirkgently

5

সেই লুপে থাকা অবস্থায় উপাদানগুলি সরানো কি কঠোর প্রয়োজন? অন্যথায় আপনি যে পয়েন্টারগুলিকে NULL মুছতে চান সেটি সেট করে রাখতে এবং সমস্ত NULL পয়েন্টারগুলি সরাতে ভেক্টরের উপর দিয়ে আরেকটি পাস করতে পারেন।

std::vector<IInventory*> inv;
inv.push_back( new Foo() );
inv.push_back( new Bar() );

for ( IInventory* &index : inv )
{
    // do some stuff
    // ok I decided I need to remove this object from inv...?
    if (do_delete_index)
    {
        delete index;
        index = NULL;
    }
}
std::remove(inv.begin(), inv.end(), NULL);

2

নেক্রোপোস্টিংয়ের জন্য দুঃখিত এবং আরও জানাচ্ছি যদি আমার সি ++ দক্ষতা আমার উত্তরের পথে আসে তবে আপনি যদি প্রতিটি আইটেমের মাধ্যমে পুনরাবৃত্তি করতে এবং সম্ভাব্য পরিবর্তনগুলি (সূচি মোছার মতো) করার চেষ্টা করেন তবে লুপের জন্য ব্যাকওয়ার্ড ব্যবহার করার চেষ্টা করুন।

for(int x=vector.getsize(); x>0; x--){

//do stuff
//erase index x

}

যখন সূচক x মুছে ফেলা হবে, পরবর্তী লুপটি আইটেমটির জন্য "শেষের" পুনরাবৃত্তির জন্য হবে। আমি সত্যিই আশা করি এটি কারও সাহায্য করেছে


কোনও নির্দিষ্ট সূচক অ্যাক্সেসের জন্য এক্স ব্যবহার করার সময় কেবল ভুলে যাবেন না, এক্স -1
এলওএল করুন

1

ঠিক আছে, আমি দেরি করে ফেলেছি, কিন্তু যাহাই হউক না কেন: দুঃখিত, না সঠিক আমি এতদূর যা পড়েন - এটা হল সম্ভব, আপনি শুধু দুই iterators প্রয়োজন:

std::vector<IInventory*>::iterator current = inv.begin();
for (IInventory* index : inv)
{
    if(/* ... */)
    {
        delete index;
    }
    else
    {
        *current++ = index;
    }
}
inv.erase(current, inv.end());

কেবল একটি পুনরাবৃত্তির নির্দেশিত মানটি কেবলমাত্র পরিবর্তন করে অন্য কোনও পুনরুক্তিকারীকে অকার্যকর করে না, তাই আমরা চিন্তা না করেই এটি করতে পারি। আসলে,std::remove_if (কমপক্ষে জিসিসি বাস্তবায়ন) খুব অনুরূপ কিছু করে (একটি ক্লাসিক লুপ ব্যবহার করে ...), কেবল কিছু মুছবে না এবং মুছবে না।

তবে সচেতন থাকুন যে এটি থ্রেড নিরাপদ নয় (!) - তবে এটি উপরের অন্যান্য সমাধানগুলির কয়েকটিতেও প্রযোজ্য ...


কি হ্যাক। এটি একটি ওভারকিল
কেস

@ কেস সত্যই? এটি ভেক্টরগুলির সাথে আপনি পেতে পারেন এটি সবচেয়ে কার্যকর অ্যালগরিদম (পরিসর ভিত্তিক লুপ বা ক্লাসিক পুনরাবৃত্তকারী লুপ কোনও ব্যাপার না): এইভাবে, আপনি প্রতিটি উপাদানটিকে একবারে একবারে সরিয়ে নিয়ে যান এবং আপনি পুরো ভেক্টরটির উপরে একবারে পুনরাবৃত্তি করেন। যদি আপনি প্রতিটি মিলন উপাদানকে erase(যদি আপনি একাধিক একক উপাদান অবশ্যই মুছে দেন) এর মাধ্যমে মুছে ফেলে থাকেন তবে আপনি পরবর্তী উপাদানগুলিকে কতবার সরিয়ে ফেলবেন এবং ভেক্টরটির উপরে পুনরাবৃত্তি করবেন ?
অ্যাকোনকাগুয়া

1

আমি উদাহরণ সহ দেখাব, নীচের উদাহরণটি ভেক্টর থেকে বিজোড় উপাদানগুলি সরান:

void test_del_vector(){
    std::vector<int> vecInt{0, 1, 2, 3, 4, 5};

    //method 1
    for(auto it = vecInt.begin();it != vecInt.end();){
        if(*it % 2){// remove all the odds
            it = vecInt.erase(it);
        } else{
            ++it;
        }
    }

    // output all the remaining elements
    for(auto const& it:vecInt)std::cout<<it;
    std::cout<<std::endl;

    // recreate vecInt, and use method 2
    vecInt = {0, 1, 2, 3, 4, 5};
    //method 2
    for(auto it=std::begin(vecInt);it!=std::end(vecInt);){
        if (*it % 2){
            it = vecInt.erase(it);
        }else{
            ++it;
        }
    }

    // output all the remaining elements
    for(auto const& it:vecInt)std::cout<<it;
    std::cout<<std::endl;

    // recreate vecInt, and use method 3
    vecInt = {0, 1, 2, 3, 4, 5};
    //method 3
    vecInt.erase(std::remove_if(vecInt.begin(), vecInt.end(),
                 [](const int a){return a % 2;}),
                 vecInt.end());

    // output all the remaining elements
    for(auto const& it:vecInt)std::cout<<it;
    std::cout<<std::endl;

}

নীচে আউটপুট আউট:

024
024
024

মনে রাখবেন, পদ্ধতিটি eraseপাস করা পুনরুদ্ধারকের পরবর্তী পুনরাবৃত্তিকে ফিরিয়ে দেবে।

থেকে এখানে , আমরা একটি আরো জেনারেট পদ্ধতি ব্যবহার করতে পারেন:

template<class Container, class F>
void erase_where(Container& c, F&& f)
{
    c.erase(std::remove_if(c.begin(), c.end(),std::forward<F>(f)),
            c.end());
}

void test_del_vector(){
    std::vector<int> vecInt{0, 1, 2, 3, 4, 5};
    //method 4
    auto is_odd = [](int x){return x % 2;};
    erase_where(vecInt, is_odd);

    // output all the remaining elements
    for(auto const& it:vecInt)std::cout<<it;
    std::cout<<std::endl;    
}

কিভাবে ব্যবহার করবেন তা দেখতে এখানে দেখুন std::remove_ifhttps://en.cppreferences.com/w/cpp/algorithm/remove


1

এই থ্রেড শিরোনামের বিরোধিতা করে, আমি দুটি পাস ব্যবহার করব:

#include <algorithm>
#include <vector>

std::vector<IInventory*> inv;
inv.push_back(new Foo());
inv.push_back(new Bar());

std::vector<IInventory*> toDelete;

for (IInventory* index : inv)
{
    // Do some stuff
    if (deleteConditionTrue)
    {
        toDelete.push_back(index);
    }
}

for (IInventory* index : toDelete)
{
    inv.erase(std::remove(inv.begin(), inv.end(), index), inv.end());
}

0

এর থেকে আরও মার্জিত সমাধান std::listহ'ল (ধরে নেওয়া উচিত যে আপনার দ্রুত এলোমেলো অ্যাক্সেসের দরকার নেই)।

list<Widget*> widgets ; // create and use this..

তারপরে .remove_ifআপনি এক লাইনে সি ++ ফ্যাক্টর সহ মুছতে পারেন :

widgets.remove_if( []( Widget*w ){ return w->isExpired() ; } ) ;

সুতরাং এখানে আমি কেবল একটি ফান্টেক্টর লিখছি যা একটি যুক্তি (টি Widget*) গ্রহণ করে । রিটার্ন মান হ'ল শর্তটি যার উপর কWidget* থেকে তালিকা থেকে ।

আমি এই বাক্যবিন্যাসটি স্পষ্টতই খুঁজে পাই। আমি মনে করি না আমি কখনো ব্যবহার করেন remove_ifজন্য এসটিডি :: ভেক্টর - এত হয় inv.begin()এবং inv.end()গোলমাল আছে আপনি ব্যবহার বন্ধ সম্ভবত ভালো আছেন একটি পূর্ণসংখ্যা-সূচক ভিত্তিক ডিলিট বা শুধু একটি প্লেইন পুরোনো নিয়মিত পুনরুক্তিকারীর ভিত্তিক মুছে ফেলে (দেখানো হয়েছে নিচে). তবে আপনি যেভাবে std::vectorযাইহোক, খুব মাঝখানে থেকে সরানো উচিত নয় , তাই এlist তালিকা মুছে ফেলার প্রায়শই মাঝারি ক্ষেত্রে এই ক্ষেত্রে পরামর্শ দেওয়া হচ্ছে।

নোট কিন্তু আমি ফোন করার সুযোগ পান নি deleteউপর Widget*খামকা সরানো হয়েছে। এটি করার জন্য, এটি দেখতে এটির মতো হবে:

widgets.remove_if( []( Widget*w ){
  bool exp = w->isExpired() ;
  if( exp )  delete w ;       // delete the widget if it was expired
  return exp ;                // remove from widgets list if it was expired
} ) ;

আপনি নিয়মিত পুনরাবৃত্ত-ভিত্তিক লুপটি এর মতো ব্যবহার করতে পারেন:

//                                                              NO INCREMENT v
for( list<Widget*>::iterator iter = widgets.begin() ; iter != widgets.end() ; )
{
  if( (*iter)->isExpired() )
  {
    delete( *iter ) ;
    iter = widgets.erase( iter ) ; // _advances_ iter, so this loop is not infinite
  }
  else
    ++iter ;
}

আপনি যদি দৈর্ঘ্য পছন্দ না করেন তবে আপনি for( list<Widget*>::iterator iter = widgets.begin() ; ...ব্যবহার করতে পারেন

for( auto iter = widgets.begin() ; ...

4
আমি মনে করি না তুমি বুঝবে কিভাবে remove_ifএকটি উপর std::vectorআসলে কাজ, এবং কিভাবে এটা হে (ঢ) এর জটিলতা রাখে।
বেন ভয়েগট

তাতে কিছু যায় আসে না। একটি মাঝখানে থেকে সরান হচ্ছে std::vectorসবসময় আপনি এক আপ মুছে পর প্রতিটি উপাদান স্লাইড, একটি তৈরীর যাচ্ছে std::listঅনেক ভালো পছন্দ।
bobobobo

4
না, এটি "প্রতিটি উপাদানকে একের উপরে স্লাইড" করবে না। remove_ifখালি শূন্যস্থানের সংখ্যা দ্বারা প্রতিটি উপাদানকে স্লাইড করে দেবে। সময় করে আপনি ক্যাশে ব্যবহারের জন্য remove_ifএকটি উপর std::vectorA থেকে সম্ভবত তূলনায় অপসারণ std::list। এবং O(1)এলোমেলো অ্যাক্সেস সংরক্ষণ করে।
বেন ভয়েগট

4
তারপরে একটি প্রশ্নের সন্ধানে আপনার দুর্দান্ত উত্তর রয়েছে। এই প্রশ্নটি তালিকার পুনরাবৃত্তি সম্পর্কে কথা বলে যা উভয় ধারকগুলির জন্য ও (এন) is এবং ও (এন) উপাদানগুলি সরান, যা উভয় পাত্রেই ও (এন)।
বেন ভয়েগট

4
প্রাক চিহ্নিতকরণ প্রয়োজন হয় না; একক পাসে এটি করা পুরোপুরি সম্ভব। আপনাকে কেবল "পরের উপাদানটি পরিদর্শন করতে হবে" এবং "পরবর্তী স্লটটি পূরণ করতে হবে" এর উপর নজর রাখতে হবে। ভবিষ্যদ্বাণীকের উপর ভিত্তি করে ফিল্টার করা তালিকার একটি অনুলিপি হিসাবে এটিকে ভাবেন। যদি ভবিষ্যদ্বাণীটি সত্য হয় তবে উপাদানটি এড়িয়ে যান, অন্যথায় এটি অনুলিপি করুন। তবে তালিকার অনুলিপিটি জায়গায় তৈরি করা হয়েছে, এবং অনুলিপি / মুভিং অনুলিপি পরিবর্তে ব্যবহৃত হয়।
বেন ভয়েগট

0

আমি মনে করি আমি নিম্নলিখিতগুলি করব ...

for (auto itr = inv.begin(); itr != inv.end();)
{
   // Do some stuff
   if (OK, I decided I need to remove this object from 'inv')
      itr = inv.erase(itr);
   else
      ++itr;
}

0

আপনি লুপের পুনরাবৃত্তির সময় পুনরাবৃত্তিকে মুছতে পারবেন না কারণ পুনরাবৃত্তির গণনাটি মিলছে না এবং কিছু পুনরাবৃত্তির পরে আপনার অবৈধ পুনরাবৃত্তি হবে।

সমাধান: 1) মূল ভেক্টরের অনুলিপি নিন 2) এই অনুলিপিটি ব্যবহার করে পুনরুক্তি করুন 2) কিছু স্টাফ করুন এবং এটি আসল ভেক্টর থেকে মুছুন।

std::vector<IInventory*> inv;
inv.push_back(new Foo());
inv.push_back(new Bar());

std::vector<IInventory*> copyinv = inv;
iteratorCout = 0;
for (IInventory* index : copyinv)
{
    // Do some stuff
    // OK, I decided I need to remove this object from 'inv'...
    inv.erase(inv.begin() + iteratorCout);
    iteratorCout++;
}  

0

একের পর এক উপাদান মোছা সহজেই N ^ 2 এর কার্যকারিতা নিয়ে যায়। মুছে ফেলা উচিত এমন উপাদানগুলি চিহ্নিত করা এবং লুপের পরে একবারে মুছে ফেলা ভাল। যদি আমি আপনার ভেক্টরে বৈধ উপাদান না হিসাবে নালপ্টার ধরে নিতে পারি, তবে

std::vector<IInventory*> inv;
// ... push some elements to inv
for (IInventory*& index : inv)
{
    // Do some stuff
    // OK, I decided I need to remove this object from 'inv'...
    {
      delete index;
      index =nullptr;
    }
}
inv.erase( std::remove( begin( inv ), end( inv ), nullptr ), end( inv ) ); 

কাজ করা উচিত.

আপনার "কিছু জিনিস করুন" যদি ভেক্টরের উপাদানগুলি পরিবর্তন না করে এবং কেবল উপাদানটি সরিয়ে ফেলতে বা রাখার সিদ্ধান্ত নিতে ব্যবহার করা হয় তবে আপনি এটিকে ল্যাম্বডায় রূপান্তর করতে পারেন (যেমন কারও আগের পোস্টে পরামর্শ দেওয়া হয়েছিল) এবং ব্যবহার করতে পারেন

inv.erase( std::remove_if( begin( inv ), end( inv ), []( Inventory* i )
  {
    // DO some stuff
    return OK, I decided I need to remove this object from 'inv'...
  } ), end( inv ) );
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.