क्या आप इसके माध्यम से पुनरावृत्ति करते हुए एक std :: सूची से तत्वों को निकाल सकते हैं?


239

मुझे ऐसा कोड मिला है जो इस तरह दिखता है:

for (std::list<item*>::iterator i=items.begin();i!=items.end();i++)
{
    bool isActive = (*i)->update();
    //if (!isActive) 
    //  items.remove(*i); 
    //else
       other_code_involving(*i);
}
items.remove_if(CheckItemNotActive);

मैं उन्हें अपडेट करने के तुरंत बाद निष्क्रिय वस्तुओं को हटा देना चाहूंगा, सूची को फिर से चलाने से बचने के लिए इनवर्टर। लेकिन अगर मैं टिप्पणी-बाहर की पंक्तियों को जोड़ता हूं, तो मुझे एक त्रुटि मिलती है जब मुझे मिलता है i++: "सूची पुनरावृत्त न होने योग्य"। मैंने कुछ विकल्पों की कोशिश की, जो कथन के लिए वृद्धि नहीं हुई, लेकिन मुझे काम करने के लिए कुछ भी नहीं मिला।

जब आप एक std :: सूची चल रहे हैं तो आइटम निकालने का सबसे अच्छा तरीका क्या है?


मैंने पीछे की ओर चलने के आधार पर कोई समाधान नहीं देखा है। मैंने ऐसे ही एक पोस्ट किया ।
sancho.s ReinstateMonicaCellio

जवाबों:


286

आपको पहले (i ++ के साथ) पुनरावृत्त को बढ़ाना होगा और फिर पिछले तत्व (जैसे, + ++ से लौटे मूल्य का उपयोग करके) को निकालना होगा। आप कोड को लूप में बदल सकते हैं जैसे:

std::list<item*>::iterator i = items.begin();
while (i != items.end())
{
    bool isActive = (*i)->update();
    if (!isActive)
    {
        items.erase(i++);  // alternatively, i = items.erase(i);
    }
    else
    {
        other_code_involving(*i);
        ++i;
    }
}

7
वास्तव में, यह काम करने की गारंटी नहीं है। "मिटा (i ++);" के साथ, हम केवल यह जानते हैं कि पूर्व-संवर्धित मान को मिटाने के लिए पारित किया गया है (), और मैं अर्ध-बृहदान्त्र से पहले बढ़ा हुआ है, जरूरी नहीं कि मिटाए जाने से पहले ()। "इट्रेटर प्रिव = i ++; इरेज़ (प्रचलित);" काम करना निश्चित है, जैसा कि रिटर्न वैल्यू का उपयोग है
जेम्स क्यूरन

58
नहीं जेम्स, मुझे कॉल करने से पहले वेतन वृद्धि की जाती है, और पिछले मान को फ़ंक्शन में पास किया जाता है। फ़ंक्शन के तर्क को फ़ंक्शन कहे जाने से पहले पूरी तरह से मूल्यांकन किया जाना है।
ब्रायन नील

28
@ जेम्स क्यूरन: यह सही नहीं है। किसी फ़ंक्शन को कॉल करने से पहले सभी तर्कों का पूर्ण मूल्यांकन किया जाता है।
मार्टिन यॉर्क

9
मार्टिन यॉर्क सही है। किसी फ़ंक्शन को कॉल करने से पहले फ़ंक्शन कॉल के सभी तर्कों का पूरी तरह से मूल्यांकन किया जाता है, कोई अपवाद नहीं। यह बस काम कैसे काम करता है। और इसका आपके foo.b (i ++) (c ++ (i ++) उदाहरण से कोई लेना-देना नहीं है (जो किसी भी मामले में अपरिभाषित है)
jalf

75
वैकल्पिक उपयोग i = items.erase(i)सुरक्षित है, क्योंकि यह एक सूची के बराबर है, लेकिन फिर भी काम करेगा यदि कोई कंटेनर को वेक्टर में बदलता है। एक वेक्टर के साथ, मिटा () छेद को भरने के लिए सब कुछ बाईं ओर ले जाता है। यदि आप अंतिम आइटम को कोड के साथ हटाने का प्रयास करते हैं जो मिटाने के बाद पुनरावृत्ति को बढ़ाता है, तो अंत बाईं ओर जाता है, और पुनरावृति दाईं ओर चलता है - अंत अतीत । और फिर आप दुर्घटना।
एरिक सेपनन

133

आप करना चाहते हैं:

i= items.erase(i);

आपके द्वारा हटाए गए पुनरावृत्ति के बाद उस स्थान को इंगित करने के लिए इट्रेटर को सही ढंग से अपडेट करेगा।


80
चेतावनी दें कि आप उस कोड को अपने फॉर-लूप में नहीं छोड़ सकते। अन्यथा आप हर बार किसी एक को निकालने के बाद एक तत्व छोड़ देंगे।
माइकल क्रिस्टोफिक

2
क्या वह मैं नहीं कर सकता -; हर बार लंघन से बचने के लिए उसके कोड का टुकड़ा?
उत्साही

1
@ सेंन्टेरिकजेक, क्या होता है अगर i==items.begin()?
एमएसएन

1
@ सेंन्टेरिकजेक, उस बिंदु पर आपको बस करना चाहिए i= items.erase(i);। यह विहित रूप है और पहले से ही उन सभी विवरणों का ध्यान रखता है।
एमएसएन

6
माइकल एक विशाल 'गेटचा' की ओर इशारा करता है, मुझे अभी उसी चीज़ से निपटना था। इससे बचने का सबसे आसान तरीका जो मुझे मिला, वह बस () लूप को थोड़ी देर के लिए विघटित कर रहा था (और थोड़ी देर के लिए सावधान रहा)
ऐनी क्विन

22

आपको क्रिस्टो के उत्तर और एमएसएन के संयोजन को करने की आवश्यकता है:

// Note: Using the pre-increment operator is preferred for iterators because
//       there can be a performance gain.
//
// Note: As long as you are iterating from beginning to end, without inserting
//       along the way you can safely save end once; otherwise get it at the
//       top of each loop.

std::list< item * >::iterator iter = items.begin();
std::list< item * >::iterator end  = items.end();

while (iter != end)
{
    item * pItem = *iter;

    if (pItem->update() == true)
    {
        other_code_involving(pItem);
        ++iter;
    }
    else
    {
        // BTW, who is deleting pItem, a.k.a. (*iter)?
        iter = items.erase(iter);
    }
}

बेशक, सबसे कुशल और सुपरकूल® एसटीएल सेवी चीज कुछ इस तरह होगी:

// This implementation of update executes other_code_involving(Item *) if
// this instance needs updating.
//
// This method returns true if this still needs future updates.
//
bool Item::update(void)
{
    if (m_needsUpdates == true)
    {
        m_needsUpdates = other_code_involving(this);
    }

    return (m_needsUpdates);
}

// This call does everything the previous loop did!!! (Including the fact
// that it isn't deleting the items that are erased!)
items.remove_if(std::not1(std::mem_fun(&Item::update)));

मैंने आपकी सुपरकूल विधि पर विचार किया, मेरी हिचकिचाहट यह थी कि remove_if को कॉल करने से यह स्पष्ट नहीं होता है कि लक्ष्य वस्तुओं को संसाधित करना है, बजाय सक्रिय लोगों की सूची से उन्हें हटाने के। (आइटम को हटाया नहीं जा रहा है क्योंकि वे केवल निष्क्रिय हो रहे हैं, अनावश्यक नहीं हैं)
AShelly

मुझे लगता है आप सही हैं। एक तरफ मैं अस्पष्टता को दूर करने के लिए 'अपडेट' का नाम बदलने का सुझाव देने के लिए इच्छुक हूं, लेकिन सच्चाई यह है कि यह कोड फंक्शनलर्स के साथ फैंसी है, लेकिन यह कुछ भी नहीं है लेकिन गैर-अस्पष्ट है।
माइक

निष्पक्ष टिप्पणी, या तो उपयोग करने के लिए लूप को समाप्त करें या अप्रयुक्त परिभाषा को हटा दें।
माइक

10

Std :: remove_if कलन विधि का प्रयोग करें।

संपादित करें: संग्रह के साथ काम इस तरह होना चाहिए: 1. संग्रह तैयार करें। 2. प्रक्रिया संग्रह।

यदि आप इस चरणों को नहीं मिलाएंगे तो जीवन आसान होगा।

  1. std :: remove_if। या सूची :: remove_if (यदि आप जानते हैं कि आप सूची के साथ काम करते हैं और TCollection के साथ नहीं)
  2. std :: for_each

2
std :: सूची में एक remove_if सदस्य फ़ंक्शन होता है, जो remove_if एल्गोरिथ्म से अधिक कुशल होता है (और "हटाने-मिटाने" मुहावरे की आवश्यकता नहीं होती है)।
ब्रायन नील

5

यहां एक forलूप का उपयोग करके एक उदाहरण दिया गया है जो सूची को बढ़ाता है और सूची के ट्रैवर्सल के दौरान हटाए जा रहे आइटम की स्थिति में पुनरावृत्त या पुनरावृत्ति को अमान्य करता है।

for(auto i = items.begin(); i != items.end();)
{
    if(bool isActive = (*i)->update())
    {
        other_code_involving(*i);
        ++i;

    }
    else
    {
        i = items.erase(i);

    }

}

items.remove_if(CheckItemNotActive);

4

क्रिस्टो के जवाब के लिए लूप संस्करण का विकल्प।

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

जवाब पूरी तरह से समय से बाहर था, मुझे पता है ...

typedef std::list<item*>::iterator item_iterator;

for(item_iterator i = items.begin(); i != items.end(); ++i)
{
    bool isActive = (*i)->update();

    if (!isActive)
    {
        items.erase(i--); 
    }
    else
    {
        other_code_involving(*i);
    }
}

1
यही मैंने भी प्रयोग किया है। लेकिन मुझे यकीन नहीं है कि अगर काम करने की गारंटी दी जाती है तो हटाए जाने वाले तत्व कंटेनर में पहला तत्व है। मेरे लिए, यह काम करता है, मुझे लगता है, लेकिन मुझे यकीन नहीं है कि यह प्लेटफार्मों भर में पोर्टेबल है।
trololo

मैंने "-1" नहीं किया था, हालांकि, सूची पुनरावृत्ति नहीं हो सकती है? कम से कम मुझे विजुअल स्टूडियो 2008 से जोर मिला है।
मीलामा जूल

जब तक लिंक्ड लिस्ट को एक सर्कुलर डबल लिंक्ड लिस्ट के रूप में लागू किया जाता है जिसमें हेड / स्टब नोड (अंत के रूप में इस्तेमाल किया जाता है) (तब) और जब खाली (शुरू और) के रूप में इस्तेमाल किया जाता है, तो यह काम करेगा। मुझे याद नहीं है कि मैं किस प्लेटफ़ॉर्म में इसका उपयोग कर रहा था, लेकिन यह मेरे लिए भी काम कर रहा था, क्योंकि ऊपर दिए गए कार्यान्वयन std :: सूची के लिए सबसे आम कार्यान्वयन है। वैसे भी, यह लगभग निश्चित है कि यह कुछ अपरिभाषित (सी ++ मानक द्वारा) व्यवहार का शोषण कर रहा था, इसलिए बेहतर इसका उपयोग न करें।
राफेल गागो

पुन: विधि एक की जरूरत है । कुछ संग्रह कार्यान्वयन, जो दावे का कारण बनते हैं। iterator cannot be decrementederaserandom access iteratorforward only iterator
जेसी चिशोल्म

@ जेसे चिशोल्म सवाल एक std :: सूची के बारे में था, एक मनमाना कंटेनर नहीं। std :: सूची मिटा और द्विदिश पुनरावृत्तियों प्रदान करता है।
राफेल गागो

4

मैंने इसे संक्षिप्त किया है, यहाँ उदाहरण के साथ तीन विधि है:

1. whileलूप का उपयोग करना

list<int> lst{4, 1, 2, 3, 5};

auto it = lst.begin();
while (it != lst.end()){
    if((*it % 2) == 1){
        it = lst.erase(it);// erase and go to next
    } else{
        ++it;  // go to next
    }
}

for(auto it:lst)cout<<it<<" ";
cout<<endl;  //4 2

2. remove_ifसूची में सदस्य funtion का उपयोग :

list<int> lst{4, 1, 2, 3, 5};

lst.remove_if([](int a){return a % 2 == 1;});

for(auto it:lst)cout<<it<<" ";
cout<<endl;  //4 2

3. सदस्य समारोह के std::remove_ifसाथ संयोजन का उपयोग कर erase:

list<int> lst{4, 1, 2, 3, 5};

lst.erase(std::remove_if(lst.begin(), lst.end(), [](int a){
    return a % 2 == 1;
}), lst.end());

for(auto it:lst)cout<<it<<" ";
cout<<endl;  //4 2

4. forलूप का उपयोग करते हुए , इटेटर को अपडेट करना चाहिए:

list<int> lst{4, 1, 2, 3, 5};

for(auto it = lst.begin(); it != lst.end();++it){
    if ((*it % 2) == 1){
        it = lst.erase(it);  erase and go to next(erase will return the next iterator)
        --it;  // as it will be add again in for, so we go back one step
    }
}

for(auto it:lst)cout<<it<<" ";
cout<<endl;  //4 2 

2

निष्कासन केवल पुनरावृत्तियों को अमान्य करता है जो हटाए गए तत्वों को इंगित करते हैं।

तो इस मामले में * i हटाने के बाद, मुझे अमान्य कर दिया गया है और आप उस पर वेतन वृद्धि नहीं कर सकते।

आप जो कर सकते हैं वह पहले तत्व के पुनरावृत्ति को सहेजना है जिसे हटाया जाना है, फिर पुनरावृत्ति को बढ़ाएं और फिर सहेजे गए को हटा दें।


2
पोस्ट-इन्क्रीमेंट का उपयोग करना कहीं अधिक सुरुचिपूर्ण है।
ब्रायन नील

2

यदि आप std::listएक कतार की तरह सोचते हैं , तो आप उन सभी वस्तुओं को हटा और संलग्न कर सकते हैं, जिन्हें आप रखना चाहते हैं, लेकिन केवल उस आइटम को हटा दें (जिसे आप नहीं चाहते हैं)। यहाँ एक उदाहरण है जहाँ मैं एक सूची से 5 को हटाना चाहता हूँ जिसमें संख्या 1-10 है ...

std::list<int> myList;

int size = myList.size(); // The size needs to be saved to iterate through the whole thing

for (int i = 0; i < size; ++i)
{
    int val = myList.back()
    myList.pop_back() // dequeue
    if (val != 5)
    {
         myList.push_front(val) // enqueue if not 5
    }
}

myList अब केवल 1-4 और 6-10 नंबर होंगे।


दिलचस्प दृष्टिकोण लेकिन मुझे डर है कि यह धीमा हो सकता है।
sg7

2

पीछे की ओर घूमने से शेष तत्वों पर एक तत्व को मिटाने के प्रभाव से बचा जाता है:

typedef list<item*> list_t;
for ( list_t::iterator it = items.end() ; it != items.begin() ; ) {
    --it;
    bool remove = <determine whether to remove>
    if ( remove ) {
        items.erase( it );
    }
}

पुनश्च: पिछड़े पुनरावृत्ति के बारे में यह देखें ।

PS2: मैंने अच्छी तरह से परीक्षण नहीं किया है अगर यह सिरों पर अच्छी तरह से मिटाने वाले तत्वों को संभालता है।


पुन: avoids the effect of erasing an element on the remaining elementsएक सूची के लिए, शायद हाँ। एक वेक्टर के लिए शायद नहीं। यह मनमाना संग्रह पर कुछ गारंटी नहीं है। उदाहरण के लिए, एक मानचित्र स्वयं को असंतुलित करने का निर्णय ले सकता है।
जेसी चिशोल्म

1

तुम लिख सकते हो

std::list<item*>::iterator i = items.begin();
while (i != items.end())
{
    bool isActive = (*i)->update();
    if (!isActive) {
        i = items.erase(i); 
    } else {
        other_code_involving(*i);
        i++;
    }
}

आप समकक्ष कोड के साथ लिख सकते हैं std::list::remove_if, जो कम क्रिया है और अधिक स्पष्ट है

items.remove_if([] (item*i) {
    bool isActive = (*i)->update();
    if (!isActive) 
        return true;

    other_code_involving(*i);
    return false;
});

std::vector::erase std::remove_ifमुहावरा जब आइटम एक सूची के बजाय एक वेक्टर हे (एन) में compexity रखना है इस्तेमाल किया जाना चाहिए - या मामले में आप सामान्य कोड लिख सकते हैं और आइटम (एक सदिश की तरह) एक आइटम को मिटाने के लिए कोई प्रभावी तरीका के साथ एक कंटेनर हो सकता है

items.erase(std::remove_if(begin(items), end(items), [] (item*i) {
    bool isActive = (*i)->update();
    if (!isActive) 
        return true;

    other_code_involving(*i);
    return false;
}));

-4

मुझे लगता है कि तुम वहाँ एक बग है, मैं इस तरह से कोड:

for (std::list<CAudioChannel *>::iterator itAudioChannel = audioChannels.begin();
             itAudioChannel != audioChannels.end(); )
{
    CAudioChannel *audioChannel = *itAudioChannel;
    std::list<CAudioChannel *>::iterator itCurrentAudioChannel = itAudioChannel;
    itAudioChannel++;

    if (audioChannel->destroyMe)
    {
        audioChannels.erase(itCurrentAudioChannel);
        delete audioChannel;
        continue;
    }
    audioChannel->Mix(outBuffer, numSamples);
}

मुझे लगता है कि यह शैली वरीयताओं के लिए नीचा दिखाया गया था, क्योंकि यह कार्यात्मक प्रतीत होता है। हां, निश्चित रूप से, (1) यह एक अतिरिक्त पुनरावृत्ति का उपयोग करता है, (2) पुनरावृत्ति वृद्धि एक लूप के लिए एक विषम जगह में होती है, जहां इसे लगाने के अच्छे कारण के बिना, (3) यह चैनल को हटाने के निर्णय के बाद काम करता है ओपी में पहले की तरह। लेकिन यह कोई गलत जवाब नहीं है।
जेसी चिशोल्म
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.