वेक्टर से आइटम निकाल रहा है, जबकि C ++ 11 रेंज में 'लूप' के लिए?


98

मेरे पास IInventory * का वेक्टर है, और मैं प्रत्येक के साथ सामान करने के लिए C ++ 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एक विधेय के साथ उपयोग कर सकते हैं जो "सामान करता है" और यदि आप हटाए गए तत्व को चाहते हैं तो यह सच है।
बेंजामिन लिंडले 4

क्या कोई कारण है कि आप सिर्फ एक इंडेक्स काउंटर नहीं जोड़ सकते हैं और फिर inv.erase (इंडेक्स) जैसी किसी चीज़ का उपयोग कर सकते हैं?
टॉमज

4
@TomJ: यह अभी भी चलना होगा।
बेन Voigt

@ टॉम और यह एक प्रदर्शन हत्यारा होगा - हर मिटा पर, आपको मिटाए जाने के बाद सभी तत्वों को स्थानांतरित करना होगा।
इवान

1
@BenVoigt ने मुझे std::listनीचे बदलने की सिफारिश की
बोबोबो

जवाबों:


95

नहीं, आप नहीं कर सकते। रेंज-आधारित 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, AFAIU, और यह बेहतर लगता है कि विधेय के साथ पुनरावृत्ति तर्क को न मिलाएं।
सेठ कार्नेगी

4
@SethCarnegie मिटा दें -गर्मी के लिए एक लंबो के साथ हटा दें सुरुचिपूर्ण ढंग से अनुमति देता है (क्योंकि यह पहले से ही C ++ 11 है)
Potatoswatter

11
इस समाधान को पसंद न करें, यह अधिकांश कंटेनरों के लिए O (N ^ 2) है। remove_ifबेहतर है।
बेन Voigt

6
इस जवाब है , सही eraseएक नया मान्य iterator देता है। यह कुशल नहीं हो सकता है, लेकिन यह काम करने की गारंटी है।
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

2
[&]स्थानीय चर के साथ "कुछ सामान" करने की अनुमति देने के लिए, एक कैप्चर-डिफ़ॉल्ट उपयुक्त होगा।
पोटाटोस्वाट्टर

2
यह एक इट्रिज़र-आधारित लूप की तुलना में कोई भी सरल नहीं दिखता है, इसके अलावा आपको अपने remove_ifसाथ घेरने के लिए याद रखना होगा .erase, अन्यथा कुछ भी नहीं होता है।
बोबोबो

3
@bobobobo अगर "इटेरेटर-आधारित लूप" से आपका मतलब सेठ कार्नेगी के जवाब से है , तो यह औसतन O (n ^ 2) है। std::remove_ifहै ओ (एन)।
ब्रांको दिमित्रिजेविक

1
@bobobobo जब तक आप वास्तव में यादृच्छिक पहुँच की आवश्यकता नहीं है
ब्रांको दिमित्रिजेविक

16

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

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कॉल के बाद आपके द्वारा अमान्य किए जाने के बाद से आप UB में चलेंगे ।


@ गिल्डरन: हाँ, सच! वह एक टाइपो था।
dirkgently

2
यह मिटा-हटा मुहावरा नहीं है। कोई कॉल नहीं है std::remove, और यह O (N ^ 2) है O (N) नहीं है।
पोटाटोस्वाटर

1
@Potatoswatter: बिल्कुल नहीं। मैं इसे हटाने के साथ समस्याओं को इंगित करने का प्रयास कर रहा था। मुझे लगता है कि मेरे शब्दांकन को पास नहीं किया गया?
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);

1

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

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

//do stuff
//erase index x

}

जब सूचकांक x मिटाते हैं, तो अगला लूप आइटम के लिए होगा "अंतिम चलना" के सामने। मुझे वाकई उम्मीद है कि इसने किसी की मदद की


बस एक निश्चित सूचकांक का उपयोग करने के लिए x का उपयोग करते समय भूल न करें, x-1 lol करें
lilbigwill99

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.cppreference.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एक पंक्ति में C ++ फ़ंक्टर के साथ हटा सकते हैं :

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

इसलिए यहाँ मैं एक फ़नकार लिख रहा हूँ जो एक तर्क ( Widget*) को स्वीकार करता है । वापसी मूल्य वह शर्त है जिस पर Widget*सूची से हटाया जाना है।

मुझे यह वाक्यविन्यास तालमेल लगता है। मुझे नहीं लगता कि मैं कभी भी std :: vectors केremove_if लिए उपयोग करूँगा - वहाँ बहुत कुछ है और शोर है कि आप शायद पूर्णांक-इंडेक्स-आधारित डिलीट का उपयोग करके बेहतर हैं या केवल एक पुराने पुराने नियमित रूप से चलने वाले-आधारित डिलीट (जैसा दिखाया गया है) नीचे)। लेकिन आपको वास्तव में बहुत अधिक के बीच से नहीं निकालना चाहिए , इसलिए सूची के विलोपन के बीच के इस मामले के लिए स्विच करने की सलाह दी जाती है।inv.begin()inv.end()std::vectorlist

नोट हालांकि मैं फोन करने के लिए एक मौका नहीं मिला 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
} ) ;

तुम भी एक नियमित itter- आधारित लूप का उपयोग कर सकते हैं जैसे:

//                                                              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() ; ...

1
मुझे नहीं लगता कि आप समझते हैं कि remove_ifएक पर std::vectorवास्तव में काम करते हैं, और यह कैसे हे (एन) के लिए जटिलता रहता है।
बेन वोइग्ट

इससे कोई फर्क नहीं पड़ता। एक के बीच से हटाना std::vectorहमेशा आपके द्वारा हटाए जाने के बाद प्रत्येक तत्व को स्लाइड करने वाला होता है, जिससे एक std::listबेहतर विकल्प बन जाता है।
बोबोबोबो

1
नहीं, यह "प्रत्येक तत्व को एक स्लाइड नहीं करेगा"। remove_ifरिक्त स्थान की संख्या से प्रत्येक तत्व को स्लाइड करेगा। जब तक आप कैश उपयोग के लिए खाते में, remove_ifएक पर std::vectorएक से अधिक संभावना Outperforms हटाने std::list। और O(1)यादृच्छिक पहुँच को संरक्षित करता है।
बेन वोइग्ट

1
फिर आपके पास एक प्रश्न की तलाश में एक महान जवाब है। यह प्रश्न सूची को पुनरावृत्त करने की बात करता है, जो दोनों कंटेनरों के लिए O (N) है। और O (N) तत्वों को हटाना, जो दोनों कंटेनरों के लिए O (N) भी है।
बेन Voigt

2
पूर्व अंकन की आवश्यकता नहीं है; एक पास में ऐसा करना पूरी तरह से संभव है। आपको बस "अगले तत्व का निरीक्षण किया जाना चाहिए" और "भरा जाने वाला अगला स्लॉट" का ट्रैक रखना होगा। इसे विधेय के आधार पर फ़िल्टर की गई सूची की एक प्रति बनाने के रूप में सोचें। यदि विधेय सही लौटता है, तो तत्व को छोड़ दें, अन्यथा इसे कॉपी करें। लेकिन सूची की प्रतिलिपि जगह में बनाई गई है, और नकल के बजाय स्वैपिंग / मूविंग का उपयोग किया जाता है।
बेन वोयगट

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 प्रदर्शन की ओर ले जाता है। उन तत्वों को चिह्नित करने के लिए बेहतर है जिन्हें लूप के बाद एक बार में मिटा दिया जाना चाहिए और मिटा देना चाहिए। अगर मैं आपके वेक्टर में मान्य तत्व नहीं है, तो nullptr मान सकते हैं

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