web-dev-qa-db-fra.com

Supprimer des éléments d'un vecteur à l'intérieur de la boucle

Je sais qu’il existe des questions similaires à celle-ci, mais je n’ai pas réussi à trouver le chemin sur mon code grâce à leur aide. Je veux simplement supprimer/supprimer un élément d'un vecteur en vérifiant un attribut de cet élément dans une boucle. Comment puis je faire ça? J'ai essayé le code suivant mais je reçois le vague message d'erreur:

La fonction 'operator =' n'est pas disponible dans 'Player'.

 for (vector<Player>::iterator it = allPlayers.begin(); it != allPlayers.end(); it++)
 {
     if(it->getpMoney()<=0) 
         it = allPlayers.erase(it);
     else 
         ++it;
 }

Que devrais-je faire? 

Mise à jour: Pensez-vous que la question vecteur :: effacer avec un membre du pointeur concerne le même problème? Ai-je besoin d'un opérateur d'affectation? Pourquoi? 

61
arjacsoh

Vous ne devez pas incrémenter it dans la boucle for:

for (vector<Player>::iterator it=allPlayers.begin(); 
                              it!=allPlayers.end(); 
                              /*it++*/) <----------- I commented it.
{

   if(it->getpMoney()<=0) 
      it = allPlayers.erase(it);
  else 
      ++it;
 }

Notez la partie commentée: it++ n’est pas nécessaire ici, car it est incrémenté dans le for-body lui-même.

Quant à la fonction d'erreur "'operator =' n'est pas disponible dans 'Player’ ", elle provient de l'utilisation de erase() qui utilise en interne operator= pour déplacer des éléments dans le vecteur. Pour pouvoir utiliser erase(), les objets de la classe Player doivent être assignables, ce qui signifie que vous devez implémenter operator= pour la classe Player.

Quoi qu'il en soit, vous devriez éviter boucle brute1 autant que possible et devrait préférer utiliser des algorithmes à la place. Dans ce cas, le populaire Erase-Remove Idiom peut simplifier ce que vous faites.

allPlayers.erase(
    std::remove_if(
        allPlayers.begin(), 
        allPlayers.end(),
        [](Player const & p) { return p.getpMoney() <= 0; }
    ), 
    allPlayers.end()
); 

1. C’est l’une des la meilleure conférence de Sean Parent que j’ai jamais regardée.

106
Nawaz
if(allPlayers.empty() == false) {
    for(int i = allPlayers.size() - 1; i >= 0; i--) {
        if(allPlayers.at(i).getpMoney() <= 0) {
            allPlayers.erase( allPlayers.begin() + i ); 
        }
    }
}

C’est ma façon de supprimer les éléments du vecteur . C’est facile à comprendre et ne nécessite aucune astuce.

11
Dawoon Yi

Oubliez la boucle et utilisez les algorithmes std ou boost range.
En utilisant Boost.Range en Lambda, cela ressemblerait à ceci:

boost::remove_if( allPlayers, bind(&Player::getpMoney, _1)<=0 );
11
TimW

Votre problème spécifique est que votre classe Player n'a pas d'opérateur d'affectation. Vous devez rendre "Player" copiable ou amovible afin de le supprimer d'un vecteur. Cela est dû au fait que le vecteur doit être contigu et qu'il doit donc réorganiser les éléments afin de combler les lacunes créées lors de la suppression d'éléments.

Également:

Utiliser l'algorithme std

allPlayers.erase(std::remove_if(allPlayers.begin(), allPlayers.end(), [](const Player& player)
{
    return player.getpMoney() <= 0;
}), allPlayers.end());

ou encore plus simple si vous avez un boost:

boost::remove_erase_if(allPlayers, [](const Player& player)
{
    return player.getpMoney() <= 0;
});

Voir la réponse de TimW si vous ne supportez pas les lambdas C++ 11.

5
ronag

Ou faites la boucle à l'envers.

for (vector<Player>::iterator it = allPlayers.end() - 1; it != allPlayers.begin() - 1; it--)
    if(it->getpMoney()<=0) 
        it = allPlayers.erase(it);
4
hhhhhhhhh

C++ 11 a introduit une nouvelle collection de fonctions qui seront utiles ici.

allPlayers.erase(
    std::remove_if(allPlayers.begin(), allPlayers.end(),
        [](auto& x) {return x->getpMoney() <= 0;} ), 
    allPlayers.end()); 

Et puis vous avez l'avantage de ne pas avoir à faire autant de changements d'éléments finaux.

3
UKMonkey

Réponse tardive, mais comme ayant vu des variantes inefficaces:

  1. std::remove ou std::remove_if est la voie à suivre.
  2. Si, pour une raison quelconque, ceux-ci ne sont pas disponibles ou ne peuvent pas être utilisés pour une autre raison, faites ce qu'ils vous cachent.

Code pour supprimer efficacement les éléments:

auto pos = container.begin();
for(auto i = container.begin(); i != container.end(); ++i)
{
    if(isKeepElement(*i)) // whatever condition...
    {
        *pos++ = *i; // will move, if move assignment is available...
    }
}
// well, std::remove(_if) stops here...
container.erase(pos, container.end());

Vous devrez peut-être écrire explicitement une telle boucle e. g. si vous avez besoin que l'itérateur lui-même détermine si l'élément doit être supprimé (le paramètre condition doit accepter une référence à un élément, rappelez-vous?), e. g. en raison d'une relation spécifique avec le successeur/prédécesseur (si cette relation est une égalité, cependant, il existe std::unique ).

0
Aconcagua