web-dev-qa-db-fra.com

Pouvez-vous supprimer des éléments d'une liste std :: list en itérant?

J'ai un code qui ressemble à ceci:

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

Je souhaite supprimer les éléments inactifs immédiatement après leur mise à jour, afin d'éviter de repasser dans la liste. Mais si j'ajoute les lignes commentées, j'obtiens une erreur lorsque j'arrive à i++: "Liste des itérateurs non incrémentable". J'ai essayé des remplaçants qui n'ont pas augmenté dans la déclaration for, mais je ne pouvais rien faire fonctionner.

Quel est le meilleur moyen de supprimer des éléments lorsque vous parcourez une liste std :: list?

229
AShelly

Vous devez d'abord incrémenter l'itérateur (avec i ++), puis supprimer l'élément précédent (par exemple, en utilisant la valeur renvoyée par i ++). Vous pouvez changer le code en une boucle while comme ceci:

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;
    }
}
269
Michael Kristofik

Vous voulez faire:

i= items.erase(i);

Cela mettra correctement à jour l'itérateur pour qu'il pointe vers l'emplacement après l'itérateur que vous avez supprimé.

125
MSN

Vous devez combiner les réponses de Kristo et celles de MSN:

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

Bien sûr, le plus efficace et le plus efficace des produits SuperCool® STL serait le suivant:

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

Utilisez l'algorithme std :: remove_if.

Edit: Le travail avec les collections devrait ressembler à: 1. préparer la collection. 2. collecte de processus.

La vie sera plus facile si vous ne mélangez pas ces étapes.

  1. std :: remove_if. ou list :: remove_if (si vous savez que vous travaillez avec list et non avec TCollection)
  2. std :: for_each
10
Mykola Golubyev

Voici un exemple d'utilisation d'une boucle for qui itère la liste et incrémente ou revalide l'itérateur dans le cas où un élément serait supprimé lors du parcours de la liste.

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);
5
David Cormack

L'alternative pour la version en boucle à la réponse de Kristo.

Vous perdez un peu d'efficacité, vous effectuez un retour en arrière puis un renvoi lors de la suppression, mais en échange de l'incrémentation supplémentaire de l'itérateur, vous pouvez faire déclarer l'itérateur dans l'étendue de la boucle et donner au code un aspect moins net. Que choisir dépend des priorités du moment.

La réponse était totalement hors du temps, je sais ...

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);
    }
}
4
Rafael Gago

Je l'ai résumé, voici les trois méthodes avec exemple:

1. en utilisant la boucle 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. en utilisant remove_if fonction membre dans la liste:

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. Utilisation de la fonction std::remove_if en combinaison avec la fonction membre 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. en utilisant la boucle for, notez la mise à jour de l'itérateur:

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 
3
Jayhello

Tu peux écrire

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

Vous pouvez écrire un code équivalent avec std::list::remove_if, qui est moins détaillé et plus explicite.

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

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

Le langage std::vector::erasestd::remove_if doit être utilisé lorsque items est un vecteur et non une liste afin de conserver la compexité sur O(n) - ou si vous écrivez du code générique et des éléments. un conteneur sans moyen efficace d'effacer des éléments isolés (comme un vecteur)

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;
}));
2
J. Kalz

La suppression invalide uniquement les itérateurs qui pointent sur les éléments supprimés.

Ainsi, dans ce cas, après avoir supprimé * i, i est invalidé et vous ne pouvez pas y incrémenter.

Ce que vous pouvez faire est d’abord enregistrer l’itérateur de l’élément à supprimer, puis incrémenter l’itérateur, puis supprimer celui qui a été enregistré.

2
anand

Si vous pensez que le std::list ressemble à une file d'attente, vous pouvez alors mettre en file d'attente tous les éléments que vous souhaitez conserver et les mettre en file d'attente, mais uniquement (et non en file d'attente) l'élément à supprimer. Voici un exemple où je veux supprimer 5 d'une liste contenant les numéros 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 n'aura plus que les chiffres 1-4 et 6-10.

2
Alex Bagg

Une itération en arrière permet d'éviter d'effacer un élément sur les éléments restants à parcourir:

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: voir this , par exemple en ce qui concerne l'itération en arrière.

PS2: Je n’ai pas fait de tests approfondis s’il gère bien les éléments d’effacement aux extrémités.

1
sancho.s