Entfernen Sie Elemente eines Vektors innerhalb der Schleife

70

Ich weiß, dass es ähnliche Fragen wie diese gibt, aber ich habe es nicht geschafft, mithilfe ihrer Hilfe den Weg in meinem Code zu finden. Ich möchte lediglich ein Element eines Vektors löschen / entfernen, indem ich ein Attribut dieses Elements in einer Schleife überprüfe. Wie kann ich das machen? Ich habe den folgenden Code ausprobiert, erhalte jedoch die vage Fehlermeldung:

Die Funktion 'operator =' ist in 'Player' nicht verfügbar.

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

Was soll ich machen?

Update: Denken Sie, dass die Frage vector :: erase with pointer member zum selben Problem gehört? Benötige ich daher einen Zuweisungsoperator? Warum?

Arjacsoh
quelle
2
Bitte beachten Sie, dass Sie mit std :: remove_if viel besser dran sein könnten. Bitte lesen Sie diesen Beitrag für Details dazu.
Verwenden Sie die in diesem Beitrag beschriebene Lösch- / Entfernungssprache .
Mukesh MV

Antworten:

133

Sie sollten itdie forSchleife nicht erhöhen :

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

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

Beachten Sie den kommentierten Teil; it++wird dort nicht benötigt, ebenso itwie das Inkrementieren im Körper selbst.

Der Fehler " 'operator =' Funktion ist in 'Player' nicht verfügbar " stammt aus der Verwendung, erase()die intern operator=zum Verschieben von Elementen im Vektor verwendet wird. Um verwendet zu werden erase(), müssen die Objekte der Klasse Playerzuweisbar sein, was bedeutet, dass Sie sie operator=für die PlayerKlasse implementieren müssen .

Auf jeden Fall sollten Sie die Rohschleife 1 so weit wie möglich vermeiden und stattdessen lieber Algorithmen verwenden. In diesem Fall kann das beliebte Erase-Remove-Idiom Ihre Arbeit vereinfachen.

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

1. Es ist eines der besten Gespräche von Sean Parent , die ich je gesehen habe.

Nawaz
quelle
1
Ich habe es versucht, aber ich erhalte den gleichen Fehler. Wenn ich die obige Schleife (zum Löschen) entferne, werden die Programme kompiliert. Folglich liegt ein Problem beim Löschen / Löschen vor. Es gibt Mitglieder der Klasse Player, die Zeiger auf andere Objekte sind. Was werden sie in diesem Fall?
Arjacsoh
1
Tatsächlich kommt der Fehler von std :: vector.erase, die den Zuweisungsoperator verwendet, um Elemente zu verschieben, um den Vektor zusammenhängend zu halten.
Ronag
Hat diese Redewendung einen Namen?
SP2danny
Das ist eine schreckliche Antwort! Iteratoren werden nach dem Löschen eines Elements ungültig !!!!
Der Quantenphysiker
8
@TheQuantumPhysicist: Ja, das stimmt, deshalb habe ich das gemacht: it = allPlayers.erase(it);Bitte sehen Sie sich die Aufgabe genau an! Oder zögern Sie nicht, eine bessere Antwort zu posten.
Nawaz
15
if(allPlayers.empty() == false) {
    for(int i = allPlayers.size() - 1; i >= 0; i--) {
        if(allPlayers.at(i).getpMoney() <= 0) {
            allPlayers.erase( allPlayers.begin() + i ); 
        }
    }
}

Auf diese Weise entferne ich Elemente im Vektor. Es ist leicht zu verstehen und braucht keine Tricks.

Dawoon Yi
quelle
3
Nur ein kurzer Kommentar: Sie können (allPlayers.empty () == false) ersetzen, indem Sie einfach (! AllPlayers.empty ()) sagen. Dies liegt daran, dass empty () einen booleschen Typ zurückgibt: Wenn der Vektor leer ist, gibt er true zurück. Die Verwendung des Operators "nicht" ist wie die Aussage "wenn es NICHT wahr ist, dass der Vektor leer ist". Nur um Ihren Code zu verschönern :)
Floella
@ Anarelle Danke!
Dawoon Yi
Dies erinnert mich daran, dass ich NICHT von Anfang an löschen sollte (i == 0). Da jedes Mal, wenn erase () aufgerufen wird, wird begin () gleichzeitig geändert. begin () + i wird entsprechend dem neuen Vektor geändert (ein Element wurde gerade gelöscht). Wenn Sie vom Ende bis zum Anfang löschen, ist dies in Ordnung. Danke :)
galian
1
Dies führt zu einem gültigen Ergebnis, ist jedoch so ineffizient wie möglich, da nachfolgende Elemente für jedes entfernte Element wiederholt nach vorne verschoben werden.
Aconcagua
10

Vergessen Sie die Schleife und verwenden Sie die Standard- oder Boost-Bereichsalgorithmen.
Mit Boost.Range en Lambda würde es so aussehen:

boost::remove_if( allPlayers, bind(&Player::getpMoney, _1)<=0 );
TimW
quelle
3
+1. Dies ist der richtige Weg !
34
-1 für eine unaufrichtige Antwort. Wie würde man zum Beispiel diese Algorithmen schreiben, ohne zu wissen, wie man es auf einer niedrigeren Ebene macht? Nicht jeder kann im Himmel der Abstraktion leben. Ungefähr so ​​nützlich wie jemand, der USE JQUERY!!1!für jemanden antwortet , der versucht, Javascript zu lernen.
Thomas Eding
3
Dieser Algorithmus ist nur nützlich, wenn Sie nur die Elemente löschen möchten. Denken Sie an ein Szenario if(condition) it = x.erase(it); else { file << *it; ++it; }. Wie Sie sehen können, wenn Sie etwas anderes tun möchten, wenn das Element nicht zum Löschen geeignet ist, können Sie es nicht verwenden remove_if. Selbst wenn Sie es verwenden, müssen Sie die Schleife möglicherweise erneut durchlaufen.
Iammilind
5

Ihr spezifisches Problem ist, dass Ihre PlayerKlasse keinen Zuweisungsoperator hat. Sie müssen "Player" entweder kopierbar oder beweglich machen, um ihn aus einem Vektor zu entfernen. Dies liegt daran, dass der Vektor zusammenhängend sein muss und daher Elemente neu anordnen muss, um Lücken zu schließen, die beim Entfernen von Elementen entstehen.

Ebenfalls:

Verwenden Sie den Standardalgorithmus

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

oder noch einfacher, wenn Sie Boost haben:

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

Lesen Sie die Antwort von TimW, wenn Sie keine Unterstützung für C ++ 11-Lambdas haben.

Ronag
quelle
Ich denke auch, dass das Problem das ist, was Sie erwähnen. Ich habe jedoch einen Zuweisungsoperator als Player & operator = (const Player & rhs) hinzugefügt. in der Player.h Datei aber ich bekomme immer noch Fehler (mit anderer Meldung). Benötige ich endlich einen Kopierkonstruktor?
Arjacsoh
1
Sie sollten auch einen Kopierkonstruktor implementieren. Es ist schwierig zu sagen, wo das Problem liegt, wenn Sie weder den relevanten Fehler noch den Code veröffentlichen.
Ronag
3

Oder machen Sie die Schleife rückwärts.

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

C ++ 11 hat eine neue Sammlung von Funktionen eingeführt, die hier von Nutzen sein werden.

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

Und dann haben Sie den Vorteil, dass Sie die Endelemente nicht so stark verschieben müssen.

UKMonkey
quelle
2
std::vector::erase(iterator)Entfernt ein einzelnes Element, auf das der Iterator zeigt. In Ihrem Beispiel wird versucht, das Element zu entfernen, auf das der von zurückgegebene std::remove_ifIterator zeigt. Dies ist ein Pass-the-End-Iterator, sodass dies mit ziemlicher Sicherheit falsch ist (und einen Absturz verursacht). Es sollte sein: allPlayers.erase(std::remove_if(...), allPlayers.end())das entfernt stattdessen alle Elemente in einem Bereich.
Ellis
0

Späte Antwort, aber als ineffiziente Varianten gesehen:

  1. std::removeoder std::remove_ifist der Weg zu gehen.
  2. Wenn diese aus irgendeinem Grund nicht verfügbar sind oder aus irgendeinem anderen Grund nicht verwendet werden können, tun Sie, was diese vor Ihnen verbergen.

Code zum effizienten Entfernen von Elementen:

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

Möglicherweise müssen Sie eine solche Schleife explizit schreiben, z. B. wenn Sie den Iterator selbst benötigen, um zu bestimmen, ob das Element entfernt werden soll (der Bedingungsparameter muss einen Verweis auf das Element akzeptieren, denken Sie daran?), Z. B. aufgrund einer bestimmten Beziehung zum Nachfolger / Vorgänger (Wenn diese Beziehung jedoch Gleichheit ist, gibt es std::unique).

Aconcagua
quelle