Использование erase-remove_if идиома

Допустим, у меня есть std::vector<std::pair<int,Direction>>,

Я пытаюсь использовать erase-remove_if идиома для удаления пар из вектора.

stopPoints.erase(std::remove_if(stopPoints.begin(),
                                stopPoints.end(),
                                [&](const stopPointPair stopPoint)-> bool { return stopPoint.first == 4; }));

Я хочу удалить все пары, для которых значение.first установлено в 4.

В моем примере у меня есть пары:

- 4, Up
- 4, Down
- 2, Up
- 6, Up

Однако после того, как я выполню erase-remove_if, у меня останется:

- 2, Up
- 6, Up
- 6, Up

Что я здесь не так делаю?

3 ответа

Решение

Правильный код:

stopPoints.erase(std::remove_if(stopPoints.begin(),
                                stopPoints.end(),
                                [&](const stopPointPair stopPoint)-> bool 
                                       { return stopPoint.first == 4; }), 
                 stopPoints.end());

Вы должны удалить диапазон, начиная с итератора, возвращенного из std::remove_if до конца вектора, а не только один элемент.

"Зачем?"

  • std::remove_if Меняет местами элементы внутри вектора, чтобы поместить все элементы, которые не соответствуют предикату, в начало контейнера.

    • Затем он возвращает итератор, который указывает на первый элемент, соответствующий предикату.

    • std::vector::erase необходимо удалить диапазон, начиная с возвращенного итератора и до конца вектора, чтобы удалить все элементы, соответствующие предикату.


Дополнительная информация: Стереть-удалить идиому (Википедия).

Метод std::vector::erase имеет две перегрузки:

iterator erase( const_iterator pos );
iterator erase( const_iterator first, const_iterator last );

Первый только удалить элемент в pos в то время как второй удалить диапазон [first, last),

Так как вы забыли last итератор в вашем вызове, первая версия выбирается по разрешению перегрузки, и вы удаляете только первую пару, сдвинутую в конец на std::remove_if, Вам нужно сделать это:

stopPoints.erase(std::remove_if(stopPoints.begin(),
                                stopPoints.end(),
                                [&](const stopPointPair stopPoint)-> bool { return stopPoint.first == 4; }), 
                 stopPoints.end());

Идиома стирания-удаления работает следующим образом: допустим, у вас есть вектор {2, 4, 3, 6, 4} и вы хотите удалить 4:

std::vector<int> vec{2, 4, 3, 6, 4};
auto it = std::remove(vec.begin(), vec.end(), 4);

Преобразует вектор в {2, 3, 6, A, B} поместив "удаленные" значения в конце (значения A а также B в конце не указывается (как если бы значение было перемещено), поэтому вы получили 6 в вашем примере) и вернуть итератор A (первое из "удаленных" значений).

Если вы делаете:

vec.erase(it)

Первая перегрузка std::vector::erase выбран, и вы удаляете только значение в it, какой A и получить {2, 3, 6, B},

Добавляя второй аргумент:

vec.erase(it, vec.end())

Выбрана вторая перегрузка, и вы стираете значение между it а также vec.end() так что оба A а также B стерты.

Я знаю, что в то время, когда был задан этот вопрос, не было С++20, поэтому, просто добавив ответ для полноты и актуальный ответ на этот вопрос, С++20 теперь имеет гораздо более чистый и менее подробный шаблон, используя std :: стереть_если .

См. общий пример кода:

      #include <vector>   
int main()
    {
        std::vector<char> cnt(10);
        std::iota(cnt.begin(), cnt.end(), '0');
     
        auto erased = std::erase_if(cnt, [](char x) { return (x - '0') % 2 == 0; });
        std::cout << erased << " even numbers were erased.\n";
    }

Фрагмент кода конкретного вопроса:

      std::erase_if(stopPoints, [&](const stopPointPair stopPoint)-> bool { return stopPoint.first == 4; });

полную информацию см. здесь:
https://en.cppreference.com/w/cpp/container/vector/erase2

Другие вопросы по тегам