Удаление элементов из вектора

Я хочу удалить элемент из вектора с помощью метода стирания. Но проблема здесь в том, что не гарантируется, что элемент встречается в векторе только один раз. Он может присутствовать несколько раз, и мне нужно удалить их все. Мой код выглядит примерно так:

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

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

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
105
0
136 826
5
Перейти к ответу Данный вопрос помечен как решенный

Ответы 5

Вызов 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);

Зачем использовать собственный функтор, если можно использовать equal_to? :-P sgi.com/tech/stl/equal_to.html

Chris Jester-Young 07.12.2008 13:24

Кстати, вызов erase с помощью remove - канонический способ сделать это.

Konrad Rudolph 07.12.2008 13:42

я думаю, что он делает именно это. но он должен использовать remove_if, если использует собственный функтор iirc. или просто используйте remove без функтора

Johannes Schaub - litb 07.12.2008 16:17

+1 Подробный код просто помог мне в соревновании по программированию, а «просто использовать идиому удалить-стереть» - нет.

user529758 10.11.2013 22:42
Ответ принят как подходящий

Используйте удалить / стереть идиому:

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 последними.
wilhelmtell 14.02.2011 18:51

Идиома «стереть-удалить» описана в правиле 32 книги Скотта Мейерса «Эффективный STL: 50 конкретных способов улучшить использование стандартной библиотеки шаблонов».

Alessandro Jacopson 01.06.2011 22:47

Как я могу обновить это, чтобы удалить настраиваемый объект, а не примитив? Я хочу что-то удалить при итерации по вектору <Cluster>.

Benjin 08.01.2013 05:43

@Benjin обновление не требуется, оно вызовет деструкторы объектов, если они существуют.

Motti 08.01.2013 21:51

Подобные «идиомы» STL заставляют меня использовать Python для небольших проектов.

Johannes Overmann 25.06.2013 19:28

@ Алессандро, почему ты так говоришь?

TankorSmash 05.12.2014 02:39

Правильно ли работает, когда вектор не содержит элементов с указанным значением?

Violet Giraffe 03.03.2015 17:13

@violetgiraffe да, remove вернет end, а затем erase ничего не сделает.

Motti 03.03.2015 18:10

@Motti Это противоречит cppreference, где сказано: Pos итератора должен быть действительным и допускающим разыменование. Таким образом, итератор end () (который действителен, но не может быть разыменован) не может использоваться в качестве значения для pos.

Louis Dionne 10.03.2017 02:38

@LouisDionne, который относится к перегрузке одного итератора, я использую перегрузку двух итераторов

Motti 13.03.2017 10:26

@Motti Это неправда. Стандарт определяет, что удаление end () является неопределенным поведением. Я проверил, что при использовании Visual Studio 15.6.7 (который поставляется со своей собственной реализацией STL) в режиме отладки метод remove выдает ошибку при задании vec.end ().

TamaMcGlinn 27.05.2018 23:20

@VioletGiraffe не удаляйте (vec.end ()) это UB!

TamaMcGlinn 27.05.2018 23:20

@TamaMcGlinn, этот код не удаляет end(), он удаляет диапазон между begin() и end(). Если begin() равен end(), в диапазоне ноль элементов и ничего не удаляется (то же самое для erase).

Motti 28.05.2018 09:31

Уважаемые комитеты C++: что не так с std :: vector <T> .remove (T & v); (так далее) ???!!! Это не редкость! 30-летний ветеран C++ возвращается с земли C# / Java после пятилетнего перерыва. Когда именно произошло это чудовище и где мне начать читать, чтобы понять, что случилось с C++?

Robin Davies 08.02.2021 18:54

В зависимости от того, почему вы это делаете, использование std :: set может быть лучше, чем std :: vector.

Это позволяет каждому элементу встречаться только один раз. Если вы добавите его несколько раз, в любом случае будет удален только один экземпляр. Это сделает операцию стирания тривиальной. Операция стирания также будет иметь меньшую временную сложность, чем в векторе, однако добавление элементов в набор происходит медленнее, поэтому это не может быть большим преимуществом.

Это, конечно, не сработает, если вас интересует, сколько раз элемент был добавлен в ваш вектор или порядок добавления элементов.

  1. Вы можете выполнять итерацию, используя доступ к индексу,

  2. Чтобы избежать сложности 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 лет спустя, кажется неуместным, Амит!

Asteroids With Wings 15.07.2020 20:51

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