Як видалити з карти під час її повторення?


177

Як видалити карту під час її повторення? подібно до:

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

Якщо я map.eraseйого використовую , ітератори недійсні



Ще більше схожих: stackoverflow.com/questions/800955/…
Kleist


Відповіді:


280

Стандартна ідіома стирання асоціативного контейнера:

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

Зауважте, що ми дійсно хочемо тут звичайного forциклу, оскільки ми змінюємо сам контейнер. Цикл на основі діапазону повинен бути суворо зарезервований для ситуацій, коли ми дбаємо лише про елементи. Синтаксис RBFL робить це зрозумілим, навіть не піддаючи контейнер усередині корпусу циклу.

Редагувати. Pre-C ++ 11, ви не можете стерти констетератори. Там ви повинні сказати:

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

Стирання елемента з контейнера не суперечить жорсткості елемента. За аналогією, воно завжди було абсолютно законним щодо того, delete pде pє вказівник на постійну. Скромність не обмежує життя; значення const в C ++ все ще можуть припинити існування.


1
"навіть не виставляючи контейнер усередині корпусу петлі", що ви маєте на увазі?
Дани

2
@Dani: Ну, протиставляй це будівництву 20 століття for (int i = 0; i < v.size(); i++). Тут ми повинні сказати v[i]всередині циклу, тобто ми повинні чітко згадати контейнер. З іншого боку, RBFL вводить циклічну змінну, яка може бути використана безпосередньо як значення, і тому ніякі знання про контейнер не потрібні всередині циклу. Це підказка щодо використання програми RBFL для циклів, які не повинні знати про контейнер. Стирання - це цілком протилежна ситуація, коли все стосується контейнера.
Керрек СБ

3
@skyhisi: Дійсно. Це одне з законних застосувань посткраста: спочатку збільшується, itщоб отримати наступний, дійсний ітератор, а потім стерти старий. Це не працює навпаки!
Керрек СБ

5
Я десь читав, що в C ++ 11 it = v.erase(it);зараз працює і для карт. Тобто стирання () на всіх асоціативних елементах тепер повертає наступний ітератор. Тож старий вираз, для якого потрібен пост-інкремент ++ в межах delete (), більше не потрібен. Це (якщо це правда) є доброю річчю, оскільки хитрість спирається на магію переокремленого пост-примноження-в-функції-виклику, "зафіксовану" новичками, щоб уникнути приросту з виклику функції або замінити його до попереднього посилення "тому що це просто стиль стилю" тощо.
Деві Морган

3
Чому б ви зателефонували it++в if і else блоки? хіба не було б достатньо, щоб зателефонували один раз після цього?
nburk

25

Я особисто віддаю перевагу цій схемі, яка є дещо чіткішою та простішою за рахунок додаткової змінної:

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

Переваги такого підходу:

  • інкремент для циклу має сенс як приріст;
  • операція стирання - це просто стирання, а не змішування з логікою приросту;
  • після першого рядка тіла циклу значення itта next_itзалишаються виправленими протягом усієї ітерації, що дозволяє легко додавати додаткові висловлювання, що стосуються них, не клацаючи на голову над тим, чи працюватимуть вони за призначенням (за винятком, звичайно, ви не можете використовувати його itпісля видалення) .

2
Я можу подумати про ще одну перевагу, якщо цикл викликає код, який видаляє повторний запис або попередній (і цикл цього не знає), він буде працювати без шкоди. Єдине обмеження - чи щось стирає те, на що вказують next_it або наступники. Повністю очищений список / карта також може бути перевірена.
Larswad

Ця відповідь проста і зрозуміла, навіть якщо цикл є складнішим і має кілька рівнів логіки, щоб вирішити, чи потрібно видаляти чи не робити інші завдання. Проте я запропонував редагування, щоб зробити його трохи простішим. "next_it" може бути встановлений на "це" в init для in, щоб уникнути помилок друку, а оскільки заяви init та ітерації встановлюють його та next_it на однакові значення, вам не потрібно говорити "next_it = це;" на початку петлі.
cdgraham

1
Майте на увазі кожного, хто використовує цю відповідь: Ви повинні мати "++ next_it" всередині циклу for, а не в ітераційному виразі. Якщо ви спробуєте перенести його в ітераційний вираз як "it = next_it ++", то на останній ітерації, коли "it" буде встановлено рівним "m.cend ()", ви спробуєте перебрати "next_it" минуле "m.cend ()", що є помилковим.
cdgraham

6

Коротше кажучи, "Як я можу видалити карту під час її повторення?"

  • Зі старою картою impl: Ви не можете
  • З новою картою impl: майже як запропонував @KerrekSB. Але є деякі проблеми з синтаксисом у тому, що він розмістив.

З GCC map impl (примітка 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

Приклад із старим та новим стилем:

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

відбитки:

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
Я не розумію. У чому проблема mi.erase(it++);?
lvella

1
@lvella див. оп. "Якщо я використовую map.erase, вона втратить ітератори".
Каш’яп

Ваш новий метод не працюватиме, якщо після стирання карта стане порожньою. У такому випадку ітератор буде визнаний недійсним. Отже, незабаром після стирання його краще вставити if(mi.empty()) break;.
Рахат Заман

4

Чернетка C ++ 20 містить функцію зручності std::erase_if.

Таким чином, ви можете використовувати цю функцію, щоб виконати її як однолінійний.

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

Досить сумно, так? Як я зазвичай це роблю, це збирати контейнер ітераторів, а не видаляти під час обходу. Потім проведіть цикл через контейнер і використовуйте 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)
}

Але після видалення одного решта буде недійсним
Дані


@Dani: Не на карті. Стирання на карті лише недійсний ітератор до стираного елемента.
UncleBens

3

Якщо припустити C ++ 11, ось корпус циклу з одним вкладишем, якщо це відповідає вашому стилю програмування:

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

Кілька інших незначних змін стилю:

  • Показуйте оголошений тип ( Map::const_iterator), коли це можливо / зручно, протягом використання auto.
  • Використовуйте usingдля типів шаблонів, щоб зробити допоміжні типи ( Map::const_iterator) легшими для читання / обслуговування.
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.