delete_if еквівалент std :: map


118

Я намагався стерти з карти різноманітні елементи, залежно від конкретних умов. Як це зробити за допомогою алгоритмів STL?

Спочатку я думав використовувати, remove_ifале це неможливо, оскільки Remove_if не працює для асоціативного контейнера.

Чи існує якийсь еквівалентний алгоритм "remove_if", який працює для карти?

Як простий варіант, я подумав прокрутити карту і видалити. Але чи переглядає карту і стирає безпечний варіант? (Як ітератори стають недійсними після стирання)

Я використовував наступний приклад:

bool predicate(const std::pair<int,std::string>& x)
{
    return x.first > 2;
}

int main(void) 
{

    std::map<int, std::string> aMap;

    aMap[2] = "two";
    aMap[3] = "three";
    aMap[4] = "four";
    aMap[5] = "five";
    aMap[6] = "six";

//      does not work, an error
//  std::remove_if(aMap.begin(), aMap.end(), predicate);

    std::map<int, std::string>::iterator iter = aMap.begin();
    std::map<int, std::string>::iterator endIter = aMap.end();

    for(; iter != endIter; ++iter)
    {
            if(Some Condition)
            {
                            // is it safe ?
                aMap.erase(iter++);
            }
    }

    return 0;
}

Що ти означає, що Remove_if не працює?
брудно

Я не можу використовувати Remove_if для пошуку елемента на карті, правда? Це дало помилку часу компіляції. Я щось пропускаю?
aJ.

Ніяк - він не працює, як працює Remove_if, переупорядковуючи послідовність, переміщуючи елементи, які не відповідають умові до кінця. Отже, він працює на T [n], але не на карті <T, U>.
MSalters

2
За допомогою C + 11 ви можете використовувати for(auto iter=aMap.begin(); iter!=aMap.end(); ){ ....}для зменшення захаращення. Відпочинок - як казали інші. Це запитання врятувало мене трохи роздвоєнням волосся ;-)
Атул Кумар

Відповіді:


111

Майже.

for(; iter != endIter; ) {
     if (Some Condition) {
          iter = aMap.erase(iter);
     } else {
          ++iter;
     }
}

Те, що ви мали спочатку, збільшило б ітератор двічі, якщо ви стерли з нього елемент; ви могли потенційно пропустити елементи, які потрібно було стерти.

Це звичайний алгоритм, який я бачив у багатьох місцях, який використовував і документував.

[EDIT] Ви праві, що ітератори недійсні після стирання, але лише ітератори, на які посилається стираний елемент, інші ітератори залишаються дійсними. Отже, використання iter++в erase()дзвінку.


4
Я збентежений; чому б ви використовували для (; ...;) замість while (...)? Крім того, хоча це, мабуть, працює, чи не повернути ітератор наступного? Тому здається, що блог if (Some Condition) повинен бути iter = aMap.erase (iter), щоб бути найбільш сумісним. Можливо, мені щось не вистачає? Мені не вистачає досвіду, який деякі з вас мають.
таксілій

86
Зауважте, що в C ++ 11 всі асоціативні контейнери, включаючи map, повертають наступний ітератор з erase(iter). Це набагато чистіше зробити iter = erase( iter ).
Potatoswatter

10
@taxilian (запізнення на роки), хоча () або для () буде працювати, але семантично люди часто використовують для () для повторення відомого діапазону, а while () для невідомої кількості циклів. Оскільки діапазон відомий у даному випадку (від початку, до кінця ), для () не було б незвичним вибором, і, ймовірно, буде більш поширеним. Але знову ж, і те, і інше було б прийнятним.
Джамін Грей

4
@taxilian Що ще важливіше: за допомогою "для" ви можете визначити своє значення ітератора ВНУТРІСТЬ циклу, щоб воно не псувалося з рештою вашої програми.
Санчіз

1
@athos Питання висловлюється пасивним голосом: "рекомендується". Універсальної рекомендації немає. Я думаю, що мій останній коментар - це найпростіший спосіб. Він включає дві копії змінної ітератора, що втрачає трохи ефективності, як хтось тут зазначив. Це ваш дзвінок, що підходить саме вам.
Potatoswatter

75

erase_if для std :: карта (та інші контейнери)

Я використовую наступний шаблон для цієї речі.

namespace stuff {
  template< typename ContainerT, typename PredicateT >
  void erase_if( ContainerT& items, const PredicateT& predicate ) {
    for( auto it = items.begin(); it != items.end(); ) {
      if( predicate(*it) ) it = items.erase(it);
      else ++it;
    }
  }
}

Це нічого не поверне, але вилучить елементи з std :: map.

Приклад використання:

// 'container' could be a std::map
// 'item_type' is what you might store in your container
using stuff::erase_if;
erase_if(container, []( item_type& item ) {
  return /* insert appropriate test */;
});

Другий приклад (дозволяє передати тестове значення):

// 'test_value' is value that you might inject into your predicate.
// 'property' is just used to provide a stand-in test
using stuff::erase_if;
int test_value = 4;  // or use whatever appropriate type and value
erase_if(container, [&test_value]( item_type& item ) {
  return item.property < test_value;  // or whatever appropriate test
});

3
@CodeAngry Спасибі - мені завжди здавалося дивним, що цього ще не було в std. Я розумію, чому він не є членом std::map, але я думаю, що щось подібне повинно бути в стандартній бібліотеці.
Залізний Спас

3
Буде додано в C ++ 20 дляstd::map та інших.
Рой Дантон


3

Цю документацію я отримав із чудової довідки SGI STL :

Карта має важливе властивість, що вставлення нового елемента в карту не приводить до недійсності ітераторів, які вказують на існуючі елементи. Видалення елемента з карти також не приводить до недійсності будь-яких ітераторів, за винятком, звичайно, ітераторів, які фактично вказують на стирається елемент.

Отже, ітератор, який у вас вказує на стираний елемент, звичайно, буде визнаний недійсним. Зробіть щось подібне:

if (some condition)
{
  iterator here=iter++;
  aMap.erase(here)
}

3
Це не відрізняється від вихідного коду. З кроком iter ++ ітератор повертає ітератор, що вказує на елемент перед приростом.
Стів Фоллі

Але це не буде визнано недійсним, оскільки ми видалимо тут позицію
1800 ІНФОРМАЦІЯ

@ 1800ІНФОРМАЦІЯ: введення виклику функції є точкою послідовності, побічний ефект збільшення збільшується раніше, ніж eraseвикликається. Тож вони справді рівноцінні. І все-таки я б дуже віддав перевагу вашій версії над оригіналом.
peterchen

Він працює для масиву чи вектора, але спричинить несподіваний результат у stl-карті.
hunter_tech

2

У вихідному коді є лише одне питання:

for(; iter != endIter; ++iter)
{
    if(Some Condition)
    {
        // is it safe ?
        aMap.erase(iter++);
    }
}

Тут iterінтенсифікується один раз у циклі for та інший раз у стиранні, що, ймовірно, закінчиться в нескінченному циклі.



1

З нижньої замітки:

http://www.sgi.com/tech/stl/PairAssociativeContainer.html

Контейнер для асоційованої пари не може надати змінних ітераторів (як визначено в вимогах Trivial Iterator), оскільки тип значення мутаційного ітератора повинен бути Assignable, а пара не Assignnable. Однак контейнер для асоційованої пари може надавати ітератори, які не є абсолютно постійними: ітератори, такі, що вираз (* i) .second = d є дійсним.


1

Перший

Карта має важливе властивість, що вставлення нового елемента в карту не приводить до недійсності ітераторів, які вказують на існуючі елементи. Видалення елемента з карти також не приводить до недійсності будь-яких ітераторів, за винятком, звичайно, ітераторів, які фактично вказують на стирається елемент.

По-друге, хороший наступний код

for(; iter != endIter; )
{
    if(Some Condition)
    {
        aMap.erase(iter++);
    }
    else
    {
        ++iter;
    }
}

Під час виклику функції параметри оцінюються перед викликом до цієї функції.

Отже, коли ітера ++ оцінюється перед видаленням дзвінка, оператор ++ ітератора поверне поточний елемент і вкаже на наступний елемент після виклику.


1

ІМХО немає remove_if()еквівалента.
Ви не можете впорядкувати карту.
Тож remove_if()не можна ставити свої пари інтересів у кінці, на які можна дзвонити erase().


Це справді прикро.
allyourcode

1

Виходячи з відповіді Iron Saviour Для тих, хто хотів би надати діапазон більше за напрямками std-функціональних ітераторів.

template< typename ContainerT, class FwdIt, class Pr >
void erase_if(ContainerT& items, FwdIt it, FwdIt Last, Pr Pred) {
    for (; it != Last; ) {
        if (Pred(*it)) it = items.erase(it);
        else ++it;
    }
}

Цікаво, якщо є якийсь спосіб втратити ContainerTелементи і отримати це від ітератора.


1
"Ідентифікатори, що починаються з підкреслення, за яким пишеться велика літера, зарезервовані для використання в реалізації."
ВАТ

0

Відповідь Стіва Фоллі я вважаю себе більш ефективною.

Ось ще одне просте, але менш ефективне рішення :

Рішення використовує remove_copy_ifдля копіювання потрібних нам значень у новий контейнер, а потім змінює вміст вихідного контейнера на значення нового:

std::map<int, std::string> aMap;

...
//Temporary map to hold the unremoved elements
std::map<int, std::string> aTempMap;

//copy unremoved values from aMap to aTempMap
std::remove_copy_if(aMap.begin(), aMap.end(), 
                    inserter(aTempMap, aTempMap.end()),
                    predicate);

//Swap the contents of aMap and aTempMap
aMap.swap(aTempMap);

2
Це здається неефективним.
allyourcode

0

Якщо ви хочете стерти всі елементи з клавішею більше 2, то найкращий спосіб

map.erase(map.upper_bound(2), map.end());

Працює лише для діапазонів, але не для будь-якого предиката.


0

Я використовую так

 std::map<int, std::string> users;    
 for(auto it = users.begin(); it <= users.end()) {
    if(<condition>){
      it = users.erase(it);
    } else {
    ++it;
    }
 }
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.