Я хочу удалить элемент из вектора с помощью метода стирания. Но проблема здесь в том, что не гарантируется, что элемент встречается в векторе только один раз. Он может присутствовать несколько раз, и мне нужно удалить их все. Мой код выглядит примерно так:
void erase(std::vector<int>& myNumbers_in, int number_in)
{
std::vector<int>::iterator iter = myNumbers_in.begin();
std::vector<int>::iterator endIter = myNumbers_in.end();
for(; iter != endIter; ++iter)
{
if (*iter == number_in)
{
myNumbers_in.erase(iter);
}
}
}
int main(int argc, char* argv[])
{
std::vector<int> myNmbers;
for(int i = 0; i < 2; ++i)
{
myNmbers.push_back(i);
myNmbers.push_back(i);
}
erase(myNmbers, 1);
return 0;
}
Этот код, очевидно, дает сбой, потому что я изменяю конец вектора во время итерации по нему. Как лучше всего этого добиться? Т.е. есть ли способ сделать это без многократной итерации вектора или создания еще одной копии вектора?





Вызов erase приведет к аннулированию итераторов, вы можете использовать:
void erase(std::vector<int>& myNumbers_in, int number_in)
{
std::vector<int>::iterator iter = myNumbers_in.begin();
while (iter != myNumbers_in.end())
{
if (*iter == number_in)
{
iter = myNumbers_in.erase(iter);
}
else
{
++iter;
}
}
}
Или вы можете использовать std :: remove_if вместе с функтором и std :: vector :: erase:
struct Eraser
{
Eraser(int number_in) : number_in(number_in) {}
int number_in;
bool operator()(int i) const
{
return i == number_in;
}
};
std::vector<int> myNumbers;
myNumbers.erase(std::remove_if (myNumbers.begin(), myNumbers.end(), Eraser(number_in)), myNumbers.end());
Вместо написания собственного функтора в этом случае вы можете использовать std :: remove:
std::vector<int> myNumbers;
myNumbers.erase(std::remove(myNumbers.begin(), myNumbers.end(), number_in), myNumbers.end());
В C++ 11 вы можете использовать лямбда вместо функтора:
std::vector<int> myNumbers;
myNumbers.erase(std::remove_if (myNumbers.begin(), myNumbers.end(), [number_in](int number){ return number == number_in; }), myNumbers.end());
В C++ 17 также доступны std :: экспериментальный :: стереть и std :: экспериментальный :: erase_if, в C++ 20 они (наконец) переименованы в std :: erase и std :: erase_if (примечание: в Visual Studio 2019 вам нужно будет изменить свою языковую версию C++ на последнюю экспериментальную версию для поддержки):
std::vector<int> myNumbers;
std::erase_if (myNumbers, Eraser(number_in)); // or use lambda
или же:
std::vector<int> myNumbers;
std::erase(myNumbers, number_in);
Кстати, вызов erase с помощью remove - канонический способ сделать это.
я думаю, что он делает именно это. но он должен использовать remove_if, если использует собственный функтор iirc. или просто используйте remove без функтора
+1 Подробный код просто помог мне в соревновании по программированию, а «просто использовать идиому удалить-стереть» - нет.
Используйте удалить / стереть идиому:
std::vector<int>& vec = myNumbers; // use shorter name
vec.erase(std::remove(vec.begin(), vec.end(), number_in), vec.end());
Что происходит, так это то, что remove сжимает элементы, которые отличаются от удаляемого значения (number_in) в начале vector, и возвращает итератор к первому элементу после этого диапазона. Затем erase удаляет эти элементы (значение которых не указано).
std::remove() сдвигает элементы таким образом, что удаляемые элементы перезаписываются. Алгоритм не изменяет размер контейнера, и если элементы n удалены, то не определено, какие элементы n последними.
Идиома «стереть-удалить» описана в правиле 32 книги Скотта Мейерса «Эффективный STL: 50 конкретных способов улучшить использование стандартной библиотеки шаблонов».
Как я могу обновить это, чтобы удалить настраиваемый объект, а не примитив? Я хочу что-то удалить при итерации по вектору <Cluster>.
@Benjin обновление не требуется, оно вызовет деструкторы объектов, если они существуют.
Подобные «идиомы» STL заставляют меня использовать Python для небольших проектов.
@ Алессандро, почему ты так говоришь?
Правильно ли работает, когда вектор не содержит элементов с указанным значением?
@violetgiraffe да, remove вернет end, а затем erase ничего не сделает.
@Motti Это противоречит cppreference, где сказано: Pos итератора должен быть действительным и допускающим разыменование. Таким образом, итератор end () (который действителен, но не может быть разыменован) не может использоваться в качестве значения для pos.
@LouisDionne, который относится к перегрузке одного итератора, я использую перегрузку двух итераторов
@Motti Это неправда. Стандарт определяет, что удаление end () является неопределенным поведением. Я проверил, что при использовании Visual Studio 15.6.7 (который поставляется со своей собственной реализацией STL) в режиме отладки метод remove выдает ошибку при задании vec.end ().
@VioletGiraffe не удаляйте (vec.end ()) это UB!
@TamaMcGlinn, этот код не удаляет end(), он удаляет диапазон между begin() и end(). Если begin() равен end(), в диапазоне ноль элементов и ничего не удаляется (то же самое для erase).
Уважаемые комитеты C++: что не так с std :: vector <T> .remove (T & v); (так далее) ???!!! Это не редкость! 30-летний ветеран C++ возвращается с земли C# / Java после пятилетнего перерыва. Когда именно произошло это чудовище и где мне начать читать, чтобы понять, что случилось с C++?
В зависимости от того, почему вы это делаете, использование std :: set может быть лучше, чем std :: vector.
Это позволяет каждому элементу встречаться только один раз. Если вы добавите его несколько раз, в любом случае будет удален только один экземпляр. Это сделает операцию стирания тривиальной. Операция стирания также будет иметь меньшую временную сложность, чем в векторе, однако добавление элементов в набор происходит медленнее, поэтому это не может быть большим преимуществом.
Это, конечно, не сработает, если вас интересует, сколько раз элемент был добавлен в ваш вектор или порядок добавления элементов.
Вы можете выполнять итерацию, используя доступ к индексу,
Чтобы избежать сложности O (n ^ 2) вы можете использовать два индекса, i - текущий индекс тестирования, j - индекс для сохранить следующий элемент и в конце цикла новый размер вектора.
код:
void erase(std::vector<int>& v, int num)
{
size_t j = 0;
for (size_t i = 0; i < v.size(); ++i) {
if (v[i] != num) v[j++] = v[i];
}
// trim vector to new size
v.resize(j);
}
В таком случае у вас нет аннулирования итераторов, сложность O (n), код очень лаконичный, и вам не нужно писать некоторые вспомогательные классы, хотя в некоторых случаях использование вспомогательных классов может принести пользу в более гибком коде.
Этот код не использует метод erase, но решает вашу задачу.
Используя чистый stl, вы можете сделать это следующим образом (это похоже на ответ Мотти):
#include <algorithm>
void erase(std::vector<int>& v, int num) {
vector<int>::iterator it = remove(v.begin(), v.end(), num);
v.erase(it, v.end());
}
Чтобы стереть 1-й элемент, вы можете использовать:
vector<int> mV{ 1, 2, 3, 4, 5 };
vector<int>::iterator it;
it = mV.begin();
mV.erase(it);
Вопрос был не в этом. Ваш ответ, сделанный 11 лет спустя, кажется неуместным, Амит!
Зачем использовать собственный функтор, если можно использовать equal_to? :-P sgi.com/tech/stl/equal_to.html