Bir std :: listesinden öğeleri yineleyerek kaldırabilir misiniz?


239

Şuna benzeyen bir kod var:

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);

Listeyi tekrar yürümekten kaçınmak için etkin olmayan öğeleri güncelledikten hemen sonra kaldırmak istiyorum. Ancak yorum satırları eklerseniz, i++"Liste yineleyici artırılmaz değil" aldığımda bir hata alıyorum . For ifadesinde artmayan bazı alternatifler denedim, ancak çalışacak bir şey alamadım.

Bir std :: listeyi yürürken eşyaları kaldırmanın en iyi yolu nedir?


Geriye doğru yinelemeye dayalı bir çözüm görmedim. Böyle bir şey gönderdim .
sancho.s ReinstateMonicaCellio

Yanıtlar:


286

Önce yineleyiciyi artırmanız (i ++ ile) ve ardından önceki öğeyi kaldırmanız gerekir (örneğin, i ++ 'dan döndürülen değeri kullanarak). Kodu bir while döngüsüne şu şekilde değiştirebilirsiniz:

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
Aslında, bunun çalışması garanti edilmez. "Erase (i ++);" ile, yalnızca önceden artırılmış değerin erase () öğesine iletildiğini ve i'nin, mutlaka delete () çağrılmasından önce değil, noktalı virgülden önce artırıldığını biliyoruz. "iterator prev = i ++; silme (önceki);" Dönüş değerini kullanacağınızdan emin olun
James Curran

58
Hayır James, silme çağrılmadan önce i artırılır ve önceki değer işleve geçirilir. Bir işlevin bağımsız değişkenleri, işlev çağrılmadan önce tam olarak değerlendirilmelidir.
Brian Neal

28
@ James Curran: Bu doğru değil. TÜM bağımsız değişkenler bir işlev çağrılmadan önce tam olarak değerlendirilir.
Martin York

9
Martin York doğrudur. Bir işlev çağrısına ilişkin tüm argümanlar, bir işlev çağrılmadan önce tam olarak değerlendirilir, istisna yoktur. İşlev bu şekilde çalışır. Ve bunun foo.b (i ++) ile hiçbir ilgisi yoktur. C (i ++) örneği (her durumda tanımlanmamıştır)
jalf

75
Alternatif kullanım i = items.erase(i)daha güvenlidir, çünkü bir liste için eşdeğerdir, ancak bir kişi kapsayıcıyı bir vektörle değiştirirse yine de çalışır. Bir vektörle, erase (), deliği doldurmak için her şeyi sola taşır. Silme işleminden sonra yineleyiciyi arttıran kodlu son öğeyi kaldırmaya çalışırsanız, uç sola hareket eder ve yineleyici sağa, sondan öteye gider . Ve sonra çöküyorsun.
Eric Seppanen

133

Yapmak istiyorsun:

i= items.erase(i);

Bu, yineleyiciyi çıkardığınız yineleyiciden sonraki konumu gösterecek şekilde doğru şekilde güncelleştirir.


80
Bu kodu for-loop'unuza bırakamayacağınız konusunda uyarınız. Aksi takdirde, her kaldırdığınızda bir öğeyi atlarsınız.
Michael Kristofik

2
yapamaz mı ben--; her zaman onun kod parçasını atlamaktan kaçınmak için?
enthusiasticgeek

1
@enthusiasticgeek, ne olur i==items.begin()?
MSN

1
@enthusiasticgeek, bu noktada sadece yapmalısınız i= items.erase(i);. Bu kanonik form ve zaten tüm bu detaylarla ilgileniyor.
MSN

6
Michael büyük bir 'yakaladım' diyor, şimdi aynı şeyle uğraşmak zorunda kaldım. Bulduğumdan kaçınmanın en kolay yöntemi, for () döngüsünü bir süre () ayrıştırmak ve artmaya dikkat etmekti
Anne Quinn

22

Krista'nun yanıtı ve MSN'lerin kombinasyonunu yapmanız gerekir:

// 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);
    }
}

Tabii ki, en verimli ve SuperCool® STL meraklısı şey şuna benzer:

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

SuperCool yönteminizi düşünmüştüm, tereddütüm remove_if çağrısının, hedefin aktif öğeler listesinden kaldırmak yerine öğeleri işlemek olduğunu netleştirmemesi oldu. (Öğeler silinmiyor, çünkü bunlar yalnızca etkin değil, gereksiz değil)
AShelly

Sanırım haklısın. Bir yandan, belirsizliği kaldırmak için 'güncelleme' adını değiştirmeyi öneriyorum, ama gerçek şu ki, bu kod functors ile süslü, ama aynı zamanda belirsiz olmayan bir şey.
Mike

Adil yorum, sonu kullanmak için while döngüsünü düzeltin veya kullanılmayan tanımı silin.
Mike

10

Std :: remove_if algoritmasını kullanın.

Düzenleme: Koleksiyonlarla çalışmak şöyle olmalıdır: 1. koleksiyon hazırlamak. 2. süreç toplama.

Bu adımları karıştırmazsanız hayat daha kolay olacaktır.

  1. std :: remove_if. veya list :: remove_if (TCollection ile değil listeyle çalıştığınızı biliyorsanız)
  2. std :: for_each

2
std :: list, remove_if algoritmasından daha verimli olan ve "remove-erase" deyimini gerektirmeyen remove_if üye işlevine sahiptir.
Brian Neal

5

Aşağıda for, listenin geçişi sırasında bir öğenin kaldırılması durumunda listeyi yineleyen ve yineleyiciyi arttıran veya yeniden doğrulayan bir döngü kullanan bir örnek verilmiştir .

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

Kristo'nun cevabına döngü versiyonu için alternatif.

Verimliliğinizi kaybedersiniz, silerken geriye ve sonra tekrar ileri gidersiniz, ancak ekstra yineleyici artışına karşılık olarak, yineleyicinin döngü kapsamında bildirilmesini ve kodun biraz daha temiz görünmesini sağlayabilirsiniz. Ne seçileceği, o anın önceliklerine bağlıdır.

Cevap tamamen zamanın dışındaydı, biliyorum ...

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
Ben de kullandım. Ancak kaldırılacak öğenin kaptaki ilk öğe olup olmadığının çalışacağından emin değilim. Bence işe yarıyor, ama platformlar arasında taşınabilir olup olmadığından emin değilim.
trololo

Ben "-1" yapmadım, ancak liste yineleyici azaltılamaz? En azından Visual Studio 2008'den bir
iddiam

Bağlantılı liste, bir kafa / saplama düğümüne (end () rbegin () olarak kullanılır ve boş bırakıldığında, begin () ve rend () olarak da kullanıldığında, dairesel çift bağlantılı bir liste olarak uygulandığı sürece bu işe yarayacaktır. Bunu hangi platformda kullandığımı hatırlayamıyorum, ancak std :: listesinin en yaygın uygulaması olduğu için benim için de çalışıyordu. Ama neyse, bunun tanımsız (C ++ standardına göre) davranışlarından faydalandığından neredeyse eminim, bu yüzden kullanmayın.
Rafael Gago

re: yöntem, ihtiyacı . Bazı toplama uygulamaları , iddiayı doğuran bir unsur sağlar. iterator cannot be decrementederaserandom access iteratorforward only iterator
Jesse Chisholm

@ Jesse Chisholm, soru keyfi bir kap değil, bir std :: listesiyle ilgiliydi. std :: list, silme ve çift yönlü yineleyiciler sağlar.
Rafael Gago

4

Özetle, işte örnek ile üç yöntem:

1. whiledöngü kullanarak

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_iflistedeki üye işlevini kullanarak :

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. fonksiyonu std::remove_ifile birleştirerek funtion kullanarak 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. fordöngü kullanarak , yineleyici güncelleme unutmayın:

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

Kaldırma işlemi, yalnızca kaldırılan öğelere işaret eden yineleyicileri geçersiz kılar.

Yani bu durumda * i çıkardıktan sonra geçersiz kılınır ve üzerinde artış yapamazsınız.

Yapabileceğiniz şey önce kaldırılacak öğenin yineleyicisini kaydetmek, daha sonra yineleyiciyi arttırmak ve ardından kaydedilen öğeyi kaldırmaktır.


2
Artım sonrası kullanımı çok daha zariftir.
Brian Neal

2

std::listBir sıraya benzer düşünüyorsanız, saklamak istediğiniz tüm öğeleri ayıklayabilir ve sıralayabilirsiniz, ancak yalnızca kaldırmak istediğiniz öğeyi ayıklayabilirsiniz (ve ayıklayabilirsiniz). İşte 1-10 sayılarını içeren bir listeden 5'i kaldırmak istediğim bir örnek ...

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 şimdi sadece 1-4 ve 6-10 sayılarına sahip olacak.


İlginç bir yaklaşım ama korkarım yavaş olsa da.
sg7

2

Geriye doğru yineleme, bir öğenin silinecek kalan öğeler üzerindeki etkisini önler:

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 );
    }
}

PS: buna , örneğin geriye doğru yinelemeye bakın.

PS2: Sonlarında iyi silme elemanlarını işleyip işlemediğini iyice test etmedim.


re: avoids the effect of erasing an element on the remaining elementsbir liste için, muhtemelen evet. Bir vektör için olmayabilir. Bu, keyfi koleksiyonlarda garanti edilen bir şey değildir. Örneğin, bir harita olabilir kendisini yeniden dengelemek karar verirler.
Jesse Chisholm

1

Yazabilirsin

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İle daha az ayrıntılı ve daha açık olan eşdeğer kod yazabilirsiniz

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

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

std::vector::erase std::remove_ifYa durumda jenerik kod yazmak ve öğeleri (bir vektör gibi) tek öğeleri silmek için etkili bir yol ile bir kap olabilir - deyim ürün O (n) de karmaşıklık ölçmelerde konstrüksiyonlar tutmak yerine listenin bir vektör olduğunda kullanılmalıdır

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

Sanırım orada bir hata var, ben bu şekilde kod:

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);
}

Sanırım bu işlevsel gibi göründüğü gibi, stil tercihleri ​​için indirildi. Evet, (1) ekstra bir yineleyici kullanıyor, (2) yineleyici artışı, oraya koymak için iyi bir neden olmadan bir döngü için garip bir yerde, (3) bunun yerine silme kararından sonra kanal çalışıyor Daha önce OP'de olduğu gibi. Ama bu yanlış bir cevap değil.
Jesse Chisholm
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.