Как удалить элемент из вектора stl с определенным значением?


146

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


2
Я знаю, что упоминал об этом несколько раз раньше, но книга Скотта Мейера « Эффективный STL» ясно освещает эти проблемы.
Роб Уэллс,


Это может быть для вас интересным чтением: en.wikipedia.org/wiki/Erase%E2%80%93remove_idiom
sergiol

Ответы:


166

std::removeна самом деле не стирает элемент из контейнера, но возвращает новый конечный итератор, который может быть передан container_type::eraseдля РЕАЛЬНОГО удаления дополнительных элементов, которые теперь находятся в конце контейнера:

std::vector<int> vec;
// .. put in some values ..
int int_to_remove = n;
vec.erase(std::remove(vec.begin(), vec.end(), int_to_remove), vec.end());

3
Зависит ли эта форма «все-в-одном» от порядка, в котором компилятор оценивает аргументы, или она vec.end()гарантированно будет одинаковой с обеих сторон вызова std::remove? Мне кажется, что при чтении других частей сети это безопасно, но об этом следует четко заявить.
dmckee --- котенок экс-модератора

1
Это замечательно. Результат vec.end()не обязательно должен быть таким же; это просто должно быть правильным (а это так).
Джим Бак

8
vec.end()должно быть таким же, но это нормально, потому что std::removeэто не меняет. Если бы он его изменил (и аннулировал старое значение), тогда возникла бы проблема: порядок оценки параметров не указан, и поэтому вы не узнаете, действителен ли второй vec.end()к тому времени, когда он используется. Причина того же проста, std::removeне меняет размер контейнера, а просто перемещает содержимое.
Стив Джессоп,

2
Я считаю этот вопрос важным, поскольку у меня такая же проблема. Но моя визуальная студия принимает std::removeтолько один аргумент; то есть const char *_Filename. Какой метод мне нужно вызвать?
Виктор

15
Это версия, removeкоторая удаляет файл. Вам необходимо включить <algorithm>, чтобы получить доступ к версии, removeкоторая имеет дело с контейнерами.
Джим Бак,

66

Если вы хотите удалить в элемент, следующий будет немного более эффективным.

std::vector<int> v;


auto it = std::find(v.begin(), v.end(), 5);
if(it != v.end())
    v.erase(it);

или вы можете избежать накладных расходов на перемещение предметов, если порядок для вас не имеет значения:

std::vector<int> v;

auto it = std::find(v.begin(), v.end(), 5);

if (it != v.end()) {
  using std::swap;

  // swap the one to be removed with the last element
  // and remove the item at the end of the container
  // to prevent moving all items after '5' by one
  swap(*it, v.back());
  v.pop_back();
}

3
Обратите внимание, что это не удалит дубликаты элемента, если они существуют, в отличие от подхода std :: remove_if.
Ден-Джейсон

15

Используйте глобальный метод std :: remove с итератором начала и конца, а затем используйте std :: vector.erase для фактического удаления элементов.

Ссылки на документацию
std :: remove http://www.cppreference.com/cppalgorithm/remove.html
std :: vector.erase http://www.cppreference.com/cppvector/erase.html

std::vector<int> v;
v.push_back(1);
v.push_back(2);

//Vector should contain the elements 1, 2

//Find new end iterator
std::vector<int>::iterator newEnd = std::remove(v.begin(), v.end(), 1);

//Erase the "removed" elements.
v.erase(newEnd, v.end());

//Vector should now only contain 2

Спасибо Джиму Баку за указание на мою ошибку.


У этого есть возможность предотвратить перемещение других элементов при стирании элемента в середине вектора, поэтому это происходит быстрее. Сначала он перемещает их в конец вектора, затем вы можете просто отбросить их с конца вектора.
Etherealone,

5

В других ответах рассказывается, как это сделать хорошо, но я подумал, что также отмечу, что не очень странно, что этого нет в векторном API: это неэффективный линейный поиск по вектору для значения, за которым следует куча копирования, чтобы удалить его.

Если вы выполняете эту операцию интенсивно, по этой причине может быть лучше рассмотреть std :: set.


5

Если у вас есть несортированный вектор, тогда вы можете просто поменять местами последний элемент вектора resize().

С упорядоченным контейнером, вы будете лучше от с std::vector::erase(). Обратите внимание, что существует std::remove()определенный in <algorithm>, но на самом деле он не стирает. (Внимательно прочтите документацию).



3

Из c ++ 20 :

Введена функция std::erase, не являющаяся членом , которая принимает в качестве входных данных вектор и удаляемое значение.

пример:

std::vector<int> v = {90,80,70,60,50};
std::erase(v,50);

2
Мало того, он перегружен для каждого конкретного контейнера!
HolyBlackCat

... и использует специфичные для контейнера свойства, например map::erase!
LF

2

См. Также std :: remove_if чтобы иметь возможность использовать предикат ...

Вот пример по ссылке выше:

vector<int> V;
V.push_back(1);
V.push_back(4);
V.push_back(2);
V.push_back(8);
V.push_back(5);
V.push_back(7);

copy(V.begin(), V.end(), ostream_iterator<int>(cout, " "));
    // The output is "1 4 2 8 5 7"

vector<int>::iterator new_end = 
    remove_if(V.begin(), V.end(), 
              compose1(bind2nd(equal_to<int>(), 0),
                       bind2nd(modulus<int>(), 2)));
V.erase(new_end, V.end()); [1]

copy(V.begin(), V.end(), ostream_iterator<int>(cout, " "));
    // The output is "1 5 7".

2
Пожалуйста, добавьте подробности в этот пост. В его нынешнем виде большая часть его контента поступает по ссылке и будет потеряна, если ссылка когда-либо сломается.
Mick MacCallum

как насчет того факта, что bind2nd - (устарело в C ++ 11) (удалено в C ++ 17)
Идан Банани

1

*

Сообщество C ++ услышало ваш запрос :)

*

C ++ 20 предоставляет простой способ сделать это сейчас. Это очень просто:

#include <vector>
...
vector<int> cnt{5, 0, 2, 8, 0, 7};
std::erase(cnt, 0);

Вы должны проверить зЬй :: сотрите и зЬй :: erase_if .

Он не только удалит все элементы значения (здесь '0'), но и сделает это за O (n) временной сложности. Это лучшее, что вы можете получить.

Если ваш компилятор не поддерживает C ++ 20, вам следует использовать идиому удаления-удаления :

#include <algorithm>
...
vec.erase(std::remove(vec.begin(), vec.end(), 0), vec.end());

0

Если вы хотите сделать это без каких-либо дополнительных услуг, включите:

vector<IComponent*> myComponents; //assume it has items in it already.
void RemoveComponent(IComponent* componentToRemove)
{
    IComponent* juggler;

    if (componentToRemove != NULL)
    {
        for (int currComponentIndex = 0; currComponentIndex < myComponents.size(); currComponentIndex++)
        {
            if (componentToRemove == myComponents[currComponentIndex])
            {
                //Since we don't care about order, swap with the last element, then delete it.
                juggler = myComponents[currComponentIndex];
                myComponents[currComponentIndex] = myComponents[myComponents.size() - 1];
                myComponents[myComponents.size() - 1] = juggler;

                //Remove it from memory and let the vector know too.
                myComponents.pop_back();
                delete juggler;
            }
        }
    }
}

0

Есть два способа, которыми вы можете использовать, в частности, стереть элемент. возьмем вектор

std :: vector < int > v;
v.push_back(10);
v.push_back(20);
v.push_back(30);
v.push_back(40);
v.push_back(40);
v.push_back(50);

1) Неэффективный способ: хотя он кажется довольно эффективным, но это не потому, что функция стирания удаляет элементы и сдвигает все элементы влево на 1., поэтому его сложность будет O (n ^ 2)

std :: vector < int > :: iterator itr = v.begin();
int value = 40;
while ( itr != v.end() )
{
   if(*itr == value)
   { 
      v.erase(itr);
   }
   else
       ++itr;
}

2) Эффективный способ (РЕКОМЕНДУЕТСЯ) : он также известен как УДАЛИТЬ - УДАЛИТЬ идиомы .

  • std :: remove преобразует данный диапазон в диапазон, в котором все элементы, которые сравниваются, не равны данному элементу, смещены в начало контейнера.
  • Итак, на самом деле не удаляйте совпадающие элементы. Он просто переместил несоответствующее начало и предоставил итератору новый действительный конец. Это просто требует сложности O (n).

вывод алгоритма удаления:

10 20 30 50 40 50 

поскольку возвращаемый тип удаления - это итератор до нового конца этого диапазона.

template <class ForwardIterator, class T>
  ForwardIterator remove (ForwardIterator first, ForwardIterator last, const T& val);

Теперь используйте функцию стирания вектора, чтобы удалить элементы от нового конца до старого конца вектора. Это требует времени O (1).

v.erase ( std :: remove (v.begin() , v.end() , element ) , v.end () );

поэтому этот метод работает за O (n)

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.