Bir haritadan tekrarlarken nasıl kaldırılır?


177

Bir haritayı tekrarlarken nasıl kaldırabilirim? sevmek:

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

Eğer kullanırsam map.eraseyineleyicileri geçersiz kılacaktır





Yanıtlar:


280

Standart ilişkisel kap silme deyimi:

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

forKonteynerin kendisini değiştirdiğimiz için burada gerçekten sıradan bir döngü istediğimizi unutmayın . Aralık tabanlı döngü, yalnızca öğeleri önemsediğimiz durumlar için kesinlikle ayrılmalıdır. RBFL sözdizimi, kabı döngü gövdesinin içinde bile açığa çıkarmadan bunu netleştirir.

Düzenle. C ++ 11 öncesi, sabit yineleyicileri silemediniz. Orada şunu söylemelisin:

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

Bir öğeyi kaptan silmek, öğenin sabitliği ile çelişmez. Benzetmeyle, bir işaretçi-sabitin delete pnerede polduğu her zaman meşru olmuştur . Constness kullanım ömrünü kısıtlamaz; C ++ 'da const değerleri var olanı durdurabilir.


1
“kabı döngü gövdesi içinde açığa çıkarmamak” ne demek istiyorsun?
Dani

2
@Dani: Bunu 20. yüzyılın inşasıyla karşılaştır for (int i = 0; i < v.size(); i++). Burada v[i]döngü içinde söylemeliyiz , yani konteynırdan açıkça bahsetmeliyiz. Öte yandan RBFL, değer olarak doğrudan kullanılabilen döngü değişkenini sunar ve bu nedenle döngü içinde kap hakkında bilgi gerekmez. Bu, konteyner hakkında bilmek zorunda olmayan döngüler için RBFL'nin amaçlanan kullanımı için bir ipucudur . Silme, tamamen kapsayıcıyla ilgili tam tersi durumdur.
Kerrek SB

3
@skyhisi: Gerçekten. Bu artım sonrası meşru kullanımlardan biridir: Bir sonraki, geçerli yineleyiciyi elde etmek için ilk artış itve sonra eskisini silmek. Diğer şekilde çalışmaz!
Kerrek SB

5
Ben C ++ 11, okumuştum it = v.erase(it);şimdi, üzerindeki silme () 'dir too.That haritaları için çalışır tüm ilişkisel elemanları şimdi bir sonraki yineleyici döndürür. Bu nedenle, delete () içinde bir artım sonrası ++ gerektiren eski çamur artık gerekli değildir. Bu, (eğer doğruysa) iyi bir şeydir, çünkü çamur, işlev çağrısı çağrısındaki arttırma sonrası büyü artışına güvendiğinden, yeni başlayanlar tarafından işlev çağrısından artışı almak veya değiştirmek için "sabitlendi" "çünkü bu sadece tarz bir şey", vb.
Dewi Morgan

3
Neden çağırır it++içinde if ve else blokların? bunlardan sonra bir kez aramak yeterli olmaz mı?
nburk

25

Ben şahsen ekstra değişken pahasına, biraz daha açık ve basit olan bu kalıbı tercih ederim:

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

Bu yaklaşımın avantajları:

  • for loop artırıcı, bir artırıcı olarak mantıklıdır;
  • silme işlemi, artış mantığı ile karıştırılmak yerine basit bir silme işlemidir;
  • döngü gövdesinin ilk satırından sonra, yineleme boyunca anlamı itve next_itsabit kalması, amaçlanan şekilde çalışıp çalışmayacaklarına dair kafa çizmeden onlara atıfta bulunan ek ifadeler kolayca eklemenize izin verir (elbette itsildikten sonra kullanamayacağınız hariç ) .

2
Eğer döngü üzerinde veya önceki olanlar yinelenen giriş siler koda çağırırsa (ve döngü bundan habersiz) başka bir avantajı aslında düşünebilirsiniz, herhangi bir zarar vermeden çalışacaktır. Tek kısıtlama, bir şeyin next_it veya halefleri tarafından işaret edilenleri silmiş olup olmadığıdır. Tamamen temizlenmiş bir liste / harita da test edilebilir.
Larswad

Bu yanıt, döngü daha karmaşık olsa ve silinip silinmeyeceğine veya diğer çeşitli görevleri yapıp yapmayacağına karar vermek için birden fazla mantık seviyesine sahip olsa bile basit ve açıktır. Yine de, biraz daha basit hale getirmek için bir düzenleme önerdim. "next_it", yazım hatalarından kaçınmak için for'un init'inde "it" olarak ayarlanabilir ve init ve yineleme ifadeleri hem onu ​​hem de next_it değerini aynı değerlere ayarladığından, "next_it = it;" demenize gerek yoktur. döngü başlangıcında.
cdgraham

1
Bu yanıtı kullanan herkesi aklınızda bulundurun: Yineleme ifadesinde değil, for döngüsünde "++ next_it" olmalıdır. Yineleme ifadesine "it = next_it ++" olarak taşımaya çalışırsanız, son yinelemede "it", "m.cend ()" değerine eşit olarak ayarlandığında, "next_it" ifadesini yinelemeye çalışırsınız. hatalı "m.cend ()" geçmiş.
cdgraham

6

Kısacası "Bir haritayı tekrarlarken nasıl kaldırabilirim?"

  • Eski harita içeriğiyle: Yapamazsınız
  • Yeni harita gösterimi ile: neredeyse @KerrekSB'nin önerdiği gibi. Ancak yayınladığı mesajda bazı sözdizimi sorunları var.

GCC harita göstergesinden (not 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

Eski ve yeni stile örnek:

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

baskılar:

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.

1
Anlamıyorum. Sorun nedir mi.erase(it++);?
lvella

1
@ lvella bkz. Msgstr "map.erase kullanırsam yineleyicileri geçersiz kılacaktır".
Kashyap

Silme işleminden sonra harita boşalırsa yeni yönteminiz çalışmaz. Bu durumda, yineleyici geçersiz kılınacaktır. Bu nedenle, sildikten hemen sonra eklemek daha iyidir if(mi.empty()) break;.
Rahat Zaman

4

C ++ 20 taslağı uygunluk işlevini içerir std::erase_if.

Böylece bu işlevi tek astar olarak yapmak için kullanabilirsiniz.

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

3

Çok üzücü, ha? Genelde yaptığım gibi, geçiş sırasında silmek yerine yineleyicilerden oluşan bir kap oluşturmaktır. Ardından kapsayıcıdan geçirin ve map.erase () kullanın

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

Ancak birini sildikten sonra geri kalanlar geçersiz olacak
Dani


@Dani: Haritada yok. Haritada silme yalnızca yineleyiciyi silinen öğeye geçersiz kılar.
AmcaBens

3

C ++ 11 varsayarsak, programlama stilinizle tutarlıysa, tek katmanlı bir döngü gövdesi:

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

Birkaç diğer küçük stil değişikliği:

  • Map::const_iteratorMümkün / uygun olduğunda, üzerinde kullanarak beyan edilen türü ( ) göster auto.
  • usingYardımcı türlerin ( Map::const_iterator) okunmasını / bakımını kolaylaştırmak için şablon türleri için kullanın .
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.