Стирання елементів з вектора


101

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

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

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

Відповіді:


167

Використовуйте ідіому видалення / видалення :

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видаляє ці елементи (значення яких не визначено).


2
std::remove()зміщує елементи таким чином, що елементи для видалення перезаписуються. Алгоритм не змінює розмір контейнера, і якщо nелементи вилучені, то не визначено, які є останні nелементи.
wilhelmtell

20
Ідіома стирання та видалення описана в пункті 32 книги "Ефективний STL: 50 конкретних способів поліпшити використання стандартної бібліотеки шаблонів" Скотта Майерса.
Алессандро Якопсон

1
@Benjin оновлення не потрібно, воно викликатиме деструктори об'єктів, якщо вони існують.
Мотті

54
Такі ідіоми STL змушують мене використовувати Python для малих проектів.
Йоганнес Оверманн

1
@LouisDionne, що стосується перевантаження одного ітератора, я використовую два перевантаження ітератора
Motti

53

Виклик стирання призведе до недійсності ітераторів, ви можете використовувати:

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 :

std::vector<int> myNumbers;
std::erase_if(myNumbers, Eraser(number_in)); // or use lambda

або:

std::vector<int> myNumbers;
std::erase(myNumbers, number_in);

2
Навіщо використовувати власний функтор, коли ви можете використовувати Equi_to? :-P sgi.com/tech/stl/equal_to.html
Кріс

3
До речі, дзвінок за eraseдопомогою remove- це канонічний спосіб зробити це.
Конрад Рудольф

1
Я думаю, що робить саме це. але він повинен використовувати remove_if, якщо використовує власний функціонал iirc. або просто скористайтесь
функцією

3
+1 Прописаний код просто допоміг мені в змаганнях з програмування, тоді як "просто використовувати ідіому" видалити-видалити "не стала.

13
  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());
}

4

Залежно від того, чому ви це робите, використання std :: set може бути кращою ідеєю, ніж std :: vector.

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

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


-1

Щоб стерти перший елемент, ви можете використовувати:

vector<int> mV{ 1, 2, 3, 4, 5 }; 
vector<int>::iterator it; 

it = mV.begin(); 
mV.erase(it); 

1
Це не було питання. Ваша відповідь на 11 років не здається актуальною, Аміт!
Астероїди з крилами
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.