Использование 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