Wie entferne ich aus einer Karte, während ich sie iteriere?

Lesezeit: 9 Minuten

Wie entferne ich aus einer Karte wahrend ich sie iteriere
Daniel

Wie entferne ich aus einer Karte, während ich sie iteriere? wie:

std::map<K, V> map;
for(auto i : map)
    if(needs_removing(i))
        // remove it from the map

Wenn ich benutze map.erase es wird die Iteratoren ungültig machen

  • Ähnlich: stackoverflow.com/questions/1038708/…

    – Christian Ammer

    22. November 2011 um 22:37 Uhr

  • Noch ähnlicher: stackoverflow.com/questions/800955/…

    – Kleist

    22. November 2011 um 22:47 Uhr

  • Und: stackoverflow.com/questions/180516/…

    – Christian Ammer

    22. November 2011 um 22:54 Uhr

  • Zum gleichen Thema: stackoverflow.com/questions/263945/…

    – Agostino

    1. März 2015 um 2:15 Uhr

1646914208 776 Wie entferne ich aus einer Karte wahrend ich sie iteriere
Kerrek SB

Das standardmäßige Löschen von assoziativen Containern:

for (auto it = m.cbegin(); it != m.cend() /* not hoisted */; /* no increment */)
{
  if (must_delete)
  {
    m.erase(it++);    // or "it = m.erase(it)" since C++11
  }
  else
  {
    ++it;
  }
}

Beachten Sie, dass wir wirklich ein gewöhnliches wollen for Schleife hier, da wir den Container selbst ändern. Die bereichsbasierte Schleife sollte ausschließlich Situationen vorbehalten sein, in denen wir uns nur um die Elemente kümmern. Die Syntax für die RBFL macht dies deutlich, indem nicht einmal der Container innerhalb des Schleifenkörpers exponiert wird.

Bearbeiten. Vor C++11 konnten Sie Konstantiteratoren nicht löschen. Da müsste man sagen:

for (std::map<K,V>::iterator it = m.begin(); it != m.end(); ) { /* ... */ }

Das Löschen eines Elements aus einem Container steht nicht im Widerspruch zur Konstanz des Elements. Analog dazu war es schon immer vollkommen legitim delete p wo p ist ein Zeiger auf eine Konstante. Beständigkeit schränkt die Lebensdauer nicht ein; const-Werte in C++ können immer noch aufhören zu existieren.

  • “Nicht einmal den Behälter im Schleifenkörper freilegen”, was meinst du?

    – Dani

    22. November 2011 um 22:34 Uhr

  • @Dani: Nun, kontrastieren Sie dies mit dem Bau des 20. Jahrhunderts for (int i = 0; i < v.size(); i++). Hier müssen wir sagen v[i] innerhalb der Schleife, dh wir müssen den Container explizit erwähnen. Die RBFL hingegen führt die Schleifenvariable ein, die direkt als Wert verwendet werden kann, sodass innerhalb der Schleife keine Kenntnis des Containers erforderlich ist. Dies ist ein Hinweis auf die beabsichtigte Verwendung des RBFL für Schleifen, die dies tun nicht muss über den Container Bescheid wissen. Löschen ist die komplette entgegengesetzte Situation, wo es nur um den Container geht.

    – Kerrek SB

    22. November 2011 um 22:41 Uhr

  • @skyhisi: In der Tat. Dies ist eine der legitimen Verwendungen des Post-Inkrements: Zuerst Zuwachs it um den nächsten gültigen Iterator zu erhalten, und dann das alte löschen. Umgekehrt geht es nicht!

    – Kerrek SB

    22. November 2011 um 22:55 Uhr


  • Ich habe irgendwo gelesen, dass in C++11, it = v.erase(it); Funktioniert jetzt auch für Maps. Das heißt, erase() an alle assoziative Elemente geben jetzt den nächsten Iterator zurück. Der alte Kludge, der ein Post-Increment++ innerhalb von delete() erforderte, wird also nicht mehr benötigt. Dies ist (falls wahr) eine gute Sache, da sich der Kludge auf die Magie des überschriebenen Post-Inkrements innerhalb eines Funktionsaufrufs stützte, die von Neulingen “behoben” wurde, um das Inkrement aus dem Funktionsaufruf zu entfernen oder es auszutauschen zu einem Vorinkrement “weil das nur eine Stilsache ist” usw.

    – Dewi Morgan

    29. Januar 2015 um 23:11 Uhr

  • warum würdest du anrufen it++ in dem if und else Blöcke? würde es nicht reichen, es einmal anzurufen nach dem diese?

    – nburk

    14. Mai 2015 um 15:21 Uhr

1646914209 843 Wie entferne ich aus einer Karte wahrend ich sie iteriere
D Coetzee

Ich persönlich bevorzuge dieses Muster, das etwas klarer und einfacher ist, auf Kosten einer zusätzlichen Variablen:

for (auto it = m.cbegin(), next_it = it; it != m.cend(); it = next_it)
{
  ++next_it;
  if (must_delete)
  {
    m.erase(it);
  }
}

Vorteile dieses Ansatzes:

  • der Inkrementor der for-Schleife ist als Inkrementor sinnvoll;
  • die Löschoperation ist eine einfache Löschung, anstatt mit einer Inkrementlogik vermischt zu werden;
  • nach der ersten Zeile des Schleifenkörpers die Bedeutung von it und next_it bleiben während der gesamten Iteration unverändert, sodass Sie problemlos zusätzliche Anweisungen hinzufügen können, die sich auf sie beziehen, ohne sich darüber den Kopf zu zerbrechen, ob sie wie beabsichtigt funktionieren (außer natürlich, dass Sie sie nicht verwenden können). it nach dem Löschen).

  • Ich kann mir tatsächlich einen weiteren Vorteil vorstellen, wenn die Schleife Code aufruft, der diesen Eintrag löscht, der über oder frühere iteriert wird (und die Schleife sich dessen nicht bewusst ist), funktioniert es ohne Schaden. Die einzige Einschränkung besteht darin, ob etwas löscht, worauf next_it oder Nachfolger zeigen. Eine vollständig gelöschte Liste/Map kann ebenfalls getestet werden.

    – Larswad

    21. November 2017 um 15:13 Uhr


  • Diese Antwort ist einfach und klar, auch wenn die Schleife komplexer ist und über mehrere Logikebenen verfügt, um zu entscheiden, ob gelöscht oder andere Aufgaben ausgeführt werden sollen. Ich habe jedoch eine Bearbeitung vorgeschlagen, um es ein wenig einfacher zu machen. “next_it” kann in der for-Initialisierung auf “it” gesetzt werden, um Tippfehler zu vermeiden, und da die init- und iteration-Anweisungen sowohl it als auch next_it auf dieselben Werte setzen, brauchen Sie nicht “next_it = it;” zu sagen. am Anfang der Schleife.

    – cdgraham

    7. November 2018 um 19:06 Uhr

  • Denken Sie an alle, die diese Antwort verwenden: Sie müssen “++next_it” innerhalb der for-Schleife und nicht im Iterationsausdruck haben. Wenn Sie versuchen, es als “it = next_it++” in den Iterationsausdruck zu verschieben, versuchen Sie bei der letzten Iteration, wenn “it” gleich “m.cend()” gesetzt wird, “next_it” zu iterieren. Vergangenheit “m.cend()”, was fehlerhaft ist.

    – cdgraham

    7. November 2018 um 19:14 Uhr

  • Ich sehe alle Vorteile als Nachteile.

    – Lukas Salich

    19. Dezember 2021 um 5:07 Uhr

Unter der Annahme von C ++ 11 ist hier ein einzeiliger Schleifenkörper, wenn dies mit Ihrem Programmierstil übereinstimmt:

using Map = std::map<K,V>;
Map map;

// Erase members that satisfy needs_removing(itr)
for (Map::const_iterator itr = map.cbegin() ; itr != map.cend() ; )
  itr = needs_removing(itr) ? map.erase(itr) : std::next(itr);

Ein paar andere kleinere Stiländerungen:

  • Deklarierten Typ anzeigen (Map::const_iterator) wenn möglich/bequem, über die Verwendung auto.
  • Verwenden using für Vorlagentypen, um Hilfstypen zu erstellen (Map::const_iterator) leichter zu lesen/pflegen.

Kurz gesagt: “Wie entferne ich aus einer Karte, während ich sie iteriere?”

  • Mit alter Karte implizieren: Das geht nicht
  • Mit neuer Karte impl: fast wie von @KerrekSB vorgeschlagen. Aber es gibt einige Syntaxprobleme in dem, was er gepostet hat.

Von der GCC-Karte impl (Anm GXX_EXPERIMENTAL_CXX0X):

#ifdef __GXX_EXPERIMENTAL_CXX0X__
      // _GLIBCXX_RESOLVE_LIB_DEFECTS
      // DR 130. Associative erase should return an iterator.
      /**
       *  @brief Erases an element from a %map.
       *  @param  position  An iterator pointing to the element to be erased.
       *  @return An iterator pointing to the element immediately following
       *          @a position prior to the element being erased. If no such 
       *          element exists, end() is returned.
       *
       *  This function erases an element, pointed to by the given
       *  iterator, from a %map.  Note that this function only erases
       *  the element, and that if the element is itself a pointer,
       *  the pointed-to memory is not touched in any way.  Managing
       *  the pointer is the user's responsibility.
       */
      iterator
      erase(iterator __position)
      { return _M_t.erase(__position); }
#else
      /**
       *  @brief Erases an element from a %map.
       *  @param  position  An iterator pointing to the element to be erased.
       *
       *  This function erases an element, pointed to by the given
       *  iterator, from a %map.  Note that this function only erases
       *  the element, and that if the element is itself a pointer,
       *  the pointed-to memory is not touched in any way.  Managing
       *  the pointer is the user's responsibility.
       */
      void
      erase(iterator __position)
      { _M_t.erase(__position); }
#endif

Beispiel mit altem und neuem Stil:

#include <iostream>
#include <map>
#include <vector>
#include <algorithm>

using namespace std;
typedef map<int, int> t_myMap;
typedef vector<t_myMap::key_type>  t_myVec;

int main() {

    cout << "main() ENTRY" << endl;

    t_myMap mi;
    mi.insert(t_myMap::value_type(1,1));
    mi.insert(t_myMap::value_type(2,1));
    mi.insert(t_myMap::value_type(3,1));
    mi.insert(t_myMap::value_type(4,1));
    mi.insert(t_myMap::value_type(5,1));
    mi.insert(t_myMap::value_type(6,1));

    cout << "Init" << endl;
    for(t_myMap::const_iterator i = mi.begin(); i != mi.end(); i++)
        cout << '\t' << i->first << '-' << i->second << endl;

    t_myVec markedForDeath;

    for (t_myMap::const_iterator it = mi.begin(); it != mi.end() ; it++)
        if (it->first > 2 && it->first < 5)
            markedForDeath.push_back(it->first);

    for(size_t i = 0; i < markedForDeath.size(); i++)
        // old erase, returns void...
        mi.erase(markedForDeath[i]);

    cout << "after old style erase of 3 & 4.." << endl;
    for(t_myMap::const_iterator i = mi.begin(); i != mi.end(); i++)
        cout << '\t' << i->first << '-' << i->second << endl;

    for (auto it = mi.begin(); it != mi.end(); ) {
        if (it->first == 5)
            // new erase() that returns iter..
            it = mi.erase(it);
        else
            ++it;
    }

    cout << "after new style erase of 5" << endl;
    // new cend/cbegin and lambda..
    for_each(mi.cbegin(), mi.cend(), [](t_myMap::const_reference it){cout << '\t' << it.first << '-' << it.second << endl;});

    return 0;
}

Drucke:

main() ENTRY
Init
        1-1
        2-1
        3-1
        4-1
        5-1
        6-1
after old style erase of 3 & 4..
        1-1
        2-1
        5-1
        6-1
after new style erase of 5
        1-1
        2-1
        6-1

Process returned 0 (0x0)   execution time : 0.021 s
Press any key to continue.

Wie entferne ich aus einer Karte wahrend ich sie iteriere
PeterT

Der C++20-Entwurf enthält die Convenience-Funktion std::erase_if.

Sie können diese Funktion also als Einzeiler verwenden.

std::map<K, V> map_obj;
//calls needs_removing for each element and erases it, if true was reuturned
std::erase_if(map_obj,needs_removing);
//if you need to pass only part of the key/value pair
std::erase_if(map_obj,[](auto& kv){return needs_removing(kv.first);});

Wie entferne ich aus einer Karte wahrend ich sie iteriere
Michael Daum

Ziemlich traurig, oder? Normalerweise baue ich einen Container mit Iteratoren auf, anstatt sie während der Traversierung zu löschen. Durchlaufen Sie dann den Container und verwenden Sie map.erase()

std::map<K,V> map;
std::list< std::map<K,V>::iterator > iteratorList;

for(auto i : map ){
    if ( needs_removing(i)){
        iteratorList.push_back(i);
    }
}
for(auto i : iteratorList){
    map.erase(*i)
}

987860cookie-checkWie entferne ich aus einer Karte, während ich sie iteriere?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy