Як видалити елемент з вектору stl з певним значенням?


145

Я переглянув документацію API для вектору stl і помітив, що в векторному класі не існує методу, який дозволяв би видалити елемент із певним значенням. Це здається звичайною операцією, і здається дивним, що для цього немає побудованого способу.


2
Я знаю, що раніше я згадував про це кілька разів, але книга Скотта Мейєра « Ефективні STL» чітко висвітлює ці проблеми.
Роб Веллс


Це може бути цікавим для вас читанням: en.wikipedia.org/wiki/Erase%E2%80%93remove_idiom
sergiol

Відповіді:


165

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що стосується контейнерів.
Джим Бак

64

Якщо ви хочете видалити в елемент, наступний буде трохи більш ефективним.

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 :: видалити 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

Дякую Джиму Баку за вказівку на мою помилку.


Це має можливість запобігати переміщенню інших елементів при стиранні елемента в середині вектора, тому це швидше. Він переміщує їх спочатку до кінця вектора, потім їх можна просто відкинути від кінця вектора.
Етереалоне

5

Інші відповіді стосуються того, як це зробити добре, але я подумав, що я також зазначив, що це не дуже дивно, що це не у векторному API: це неефективний, лінійний пошук за вектором для значення, за яким слідує купа копіювання, щоб видалити його.

Якщо ви робите цю операцію інтенсивно, з цього приводу може бути варто розглянути std :: set.


5

Якщо у вас є несортований вектор, тоді ви можете просто поміняти місцями з останнім векторним елементом resize().

З впорядкованим контейнером, ви будете краще від с std::vector::erase(). Зауважте, що є std::remove()визначений в <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
Будь ласка, додайте детальніше до цієї публікації. На сьогоднішній день більшість його вмісту надходить із посилання, і воно втрачається, якщо посилання коли-небудь перерветься.
Мік Маккаллум

що з приводу факту bind2nd - (застаріле в C ++ 11) (видалено у C ++ 17)
Ідан Банані

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 :: delete перетворює заданий діапазон в діапазон, коли всі елементи, що порівнюють, не дорівнює даному елементу, зміщеному до початку контейнера.
  • Отже, насправді не видаляйте відповідні елементи. Він просто перемістив невідповідний на старт і дає ітератор до нового дійсного кінця. Це просто вимагає 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)


0

*

Спільнота C ++ почула ваш запит :)

*

C ++ 20 забезпечує простий спосіб зробити це зараз. Він стає таким же простим, як:

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

Слід перевірити std :: erase та std :: erase_if .

Він не тільки видалить усі елементи значення (тут '0'), але і зробить це в O (n) часовій складності. Це найкраще, що можна отримати.

Якщо ваш компілятор не підтримує C ++ 20, вам слід використовувати ідіому стирання-видалення :

#include <algorithm>
...
vec.erase(std::remove(vec.begin(), vec.end(), 0), vec.end());
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.