Як стерти елемент з std :: vector <за індексом?


508

У мене є std :: vector <int>, і я хочу видалити n-й елемент. Як це зробити?

std::vector<int> vec;

vec.push_back(6);
vec.push_back(-17);
vec.push_back(12);

vec.erase(???);

4
Подумайте про використання std :: deque, який забезпечує вставлення та видалення з обох кінців.
Даріо

40
Ні, не думайте використовувати deque тільки тому, що ви можете видалити елемент, це дійсно погана порада. Існує цілий ряд причин, чому ви можете використовувати deque або вектор. Це правда, що видалення елемента з вектора може бути дорогим, - особливо, якщо вектор великий, але немає причин думати, що дека була б кращою за вектор з прикладу коду, який ви тільки що опублікували.
Сова

6
Наприклад, якщо у вас є графічна програма, де ви відображаєте "список" речей, куди інтерактивно вставляєте / виймаєте речі, розгляньте, що ви переглядаєте список 50-100 разів кожну секунду для їх відображення, і ви додаєте / видаляєте речі кілька разів щохвилини. Тож реалізація "списку" як вектора, мабуть, кращий варіант з точки зору повної ефективності.
Мішель Білло

Відповіді:


705

Щоб видалити один елемент, ви можете:

std::vector<int> vec;

vec.push_back(6);
vec.push_back(-17);
vec.push_back(12);

// Deletes the second element (vec[1])
vec.erase(vec.begin() + 1);

Або видалити одразу більше одного елемента:

// Deletes the second through third elements (vec[1], vec[2])
vec.erase(vec.begin() + 1, vec.begin() + 3);

50
Відзначимо також , двоичная operator+це НЕ обов'язково визначається для ітераторів на інших типах контейнерів, як list<T>::iterator(ви не можете зробити list.begin() + 2на умовах std::list, ви повинні використовувати std::advanceдля цього)
bobobobo

ви заявляєте, що "+1" - це перший елемент myVector [0] або фактичне положення myVector [1]
Карл Моррісон,

2
Заздалегідь потрібно зберегти ітератор у змінній. Якщо ви використовуєте std :: next, ви можете зробити це в одному рядку: vec.erase (next (start (vec), 123));
Дані

8
Дякую всім, хто відповів. Що ми думаємо про дизайн класу, коли така проста операція, як видалення елемента, вимагає, щоб хтось прийшов до StackOverflow?
П’єр

5
@Pierre, оскільки числовий індекс конкретного елемента не є первинною моделлю доступу, це ітератор . Усі функції, що розглядають елементи контейнера, використовують ітератори цього контейнера. Напр.std::find_if
Caleth

212

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

vec.erase(vec.begin() + index);

коли ви хочете стерти лише один елемент.


3
Але ця проблема з’являється незалежно від того, скільки елементів у вас є.
Zyx 2000,

15
якщо є лише один елемент, індекс дорівнює 0, і тоді ви отримуєте, vec.begin()що є дійсним.
Енн Квін

28
Я б хотів, щоб хтось згадав, що vec.erase(0)це не працює, але vec.erase(vec.begin()+0)(або без +0). Інакше я не отримую відповідного виклику функції, саме тому я прийшов сюди
qrtLs

@qrtLs vec.erase(0)може насправді компілюватися, якщо 0трапляється інтерпретувати як нульову константу вказівника ...
LF

57
template <typename T>
void remove(std::vector<T>& vec, size_t pos)
{
    std::vector<T>::iterator it = vec.begin();
    std::advance(it, pos);
    vec.erase(it);
}

2
Макс, що робить цю функцію кращою, ніж: template <typename T> void remove(std::vector<T>& vec, size_t pos) { vec.erase(vec.begin + pos); }я не кажу, що і краще, просто просити з особистого інтересу і повернути найкращий результат, який це питання може отримати.

13
@JoeyvG: Оскільки vector<T>::iteratorітератор із випадковим доступом, ваша версія чудова і, можливо, трохи зрозуміліша. Але версія, яку Макс опублікував, повинна спрацьовувати чудово, якщо ви поміняєте контейнер на інший, який не підтримує ітераторів з випадковим доступом
Лілі Баллард,

2
Це найкраще відповідь, оскільки це стосується і інших форматів контейнерів. Ви також можете використовувати std :: next ().
Бім

Набагато кращий підхід, оскільки він не покладається на внутрішність контейнера.
BartoszKP

std :: аванс потрібен лише у тому випадку, якщо ви думаєте, що це не буде вектор, тобто список. Але як ви вже вказали, що оператор + не буде простішим? Відповідно до цього stackoverflow.com/questions/1668088/… можливий приріст у роботі з оператором +
Ніл Макгілл

14

eraseМетод буде використовуватися двома способами:

  1. Стирання одного елемента:

    vector.erase( vector.begin() + 3 ); // Deleting the fourth element
  2. Стирання діапазону елементів:

    vector.erase( vector.begin() + 3, vector.begin() + 5 ); // Deleting from fourth element to sixth element

7
Це повторна відповідь майже через 7 років після прийнятої відповіді. Будь ласка, не робіть цього.
AlastairG

10

Насправді eraseфункція працює для двох профілів:

  • Видалення одного елемента

    iterator erase (iterator position);
  • Видалення діапазону елементів

    iterator erase (iterator first, iterator last);

Оскільки std :: vec.begin () позначає початок контейнера, і якщо ми хочемо видалити i-й елемент з нашого вектора, ми можемо використовувати:

vec.erase(vec.begin() + index);

Якщо ви придивитесь уважно, vec.begin () - це лише вказівник на вихідне положення нашого вектора і додає значення i до нього збільшує вказівник на позицію i, тож натомість ми можемо отримати доступ до вказівника до i-го елемента:

&vec[i]

Тож ми можемо написати:

vec.erase(&vec[i]); // To delete the ith element

6
-1 Останній рядок не компілюється (принаймні у VS2017). Код передбачає, що вектор :: ітератор неявно може бути сконструйований із необробленого покажчика, який не вимагається стандартом.
CuriousGeorge

1
Це стосується особливо ітераторів налагодження
Nishant Singh

9

Якщо у вас є не упорядкований вектор, ви можете скористатися тим, що він не упорядкований, і використовувати щось, що я бачив від Дена Хіггінса в CPPCON

template< typename TContainer >
static bool EraseFromUnorderedByIndex( TContainer& inContainer, size_t inIndex )
{
    if ( inIndex < inContainer.size() )
    {
        if ( inIndex != inContainer.size() - 1 )
            inContainer[inIndex] = inContainer.back();
        inContainer.pop_back();
        return true;
    }
    return false;
}

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


Я думаю, що це найкраща відповідь, якщо вектор не упорядкований. Це не покладається на припущення, що iterator + indexнасправді поверне вам позицію ітератора в цьому індексі, що не стосується всіх ітерабельних контейнерів. Це також постійна складність замість лінійної, скориставшись зворотним вказівником.
theferrit32

1
Це повністю потрібно додати до std lib як unordered_removeі unordered_remove_if… якщо це не було, і я його не пропустив, що трапляється все частіше в ці дні :)
Буде Кроуфорд,

Якщо ви б запропонували замість копіювання призначити використання переміщення-призначення або заміни.
Carsten S

std::removeвпорядковує контейнер, щоб усі елементи, які потрібно видалити, були в кінці, не потрібно робити це вручну, як це використовується C ++ 17.
keith

@keith як це std::removeдопомагає? cppreference стверджує, що навіть у C ++ 17 для всіх removeперевантажень потрібен предикат, і жоден не приймає індекс.
Пол Дю Буа

4

Якщо ви працюєте з великими векторами (розмір> 100 000) і хочете видалити багато елементів, я рекомендую зробити щось подібне:

int main(int argc, char** argv) {

    vector <int> vec;
    vector <int> vec2;

    for (int i = 0; i < 20000000; i++){
        vec.push_back(i);}

    for (int i = 0; i < vec.size(); i++)
    {
        if(vec.at(i) %3 != 0)
            vec2.push_back(i);
    }

    vec = vec2;
    cout << vec.size() << endl;
}

Код приймає кожне число у vec, яке неможливо розділити на 3, і копіює його у vec2. Після цього він копіює vec2 in vec. Це досить швидко. Для обробки 20 000 000 елементів цей алгоритм займає лише 0,8 сек!

Я робив те ж саме з методом erase, і це займає багато і багато часу:

Erase-Version (10k elements)  : 0.04 sec
Erase-Version (100k elements) : 0.6  sec
Erase-Version (1000k elements): 56   sec
Erase-Version (10000k elements): ...still calculating (>30 min)

6
як це відповідає на питання?
Regis Portalez

4
Цікаво, але не стосується питання!
Родді

Чи не швидший алгоритм на місці?
користувач202729

2
that std :: remove_if (+ стерти)
RiaD

3

Для видалення елемента використовуйте наступний спосіб:

// declaring and assigning array1 
std:vector<int> array1 {0,2,3,4};

// erasing the value in the array
array1.erase(array1.begin()+n);

Для отримання більш широкого огляду ви можете відвідати: http://www.cplusplus.com/reference/vector/vector/erase/


Подумайте про використання cppreference . Дивіться це , це тощо
LF

3

Я пропоную прочитати це, оскільки я вважаю, що це те, що ви шукаєте. https://en.wikipedia.org/wiki/Erase%E2%80%93remove_idiom

Якщо ви використовуєте, наприклад

 vec.erase(vec.begin() + 1, vec.begin() + 3);

ви будете стирати n-й елемент вектора, але коли ви стираєте другий елемент, всі інші елементи вектора будуть зміщені, а розмір вектора - -1. Це може бути проблемою, якщо ви переглядаєте вектор, оскільки розмір вектора () зменшується. Якщо у вас є такі проблеми, як надане посилання, пропонується використовувати існуючий алгоритм у стандартній бібліотеці C ++. і "видалити" або "видалити_іф".

Сподіваюся, що це допомогло


0

Попередні відповіді передбачають, що у вас завжди є підписаний індекс. На жаль, std::vectorвикористовується size_typeдля індексації та difference_typeдля арифметики ітератора, тому вони не працюють разом, якщо у вас включено "-Wconversion" та друзів. Це ще один спосіб відповісти на питання, в той час як можна обробляти як підписані, так і непідписані:

Видалити:

template<class T, class I, class = typename std::enable_if<std::is_integral<I>::value>::type>
void remove(std::vector<T> &v, I index)
{
    const auto &iter = v.cbegin() + gsl::narrow_cast<typename std::vector<T>::difference_type>(index);
    v.erase(iter);
}

Взяти:

template<class T, class I, class = typename std::enable_if<std::is_integral<I>::value>::type>
T take(std::vector<T> &v, I index)
{
    const auto &iter = v.cbegin() + gsl::narrow_cast<typename std::vector<T>::difference_type>(index);

    auto val = *iter;
    v.erase(iter);

    return val;
}

0

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

vector<int> ar(n);
ar.erase(remove(ar.begin(), ar.end()), (place your value here from vector array));

це видалить ваше значення звідси. Дякую


0

Як щодо цього?

void squeeze(vector<int> &v)
{
    int j = 0;
    for (int i = 1; i < v.size(); i++)
        if (v[i] != v[j] && ++j != i)
            v[j] = v[i];
    v.resize(j + 1);
}

-4

найшвидший спосіб (для програмування конкурсів за часовою складністю () = константа)

можна стерти 100M елемент за 1 секунду;

    vector<int> it = (vector<int>::iterator) &vec[pos];
    vec.erase(it);

і найбільш читаний спосіб: vec.erase(vec.begin() + pos);


2
Це дуже не портативно; він буде працювати з libstdc ++, але не libc ++, а не з MSVC. vector<int>::iteratorне обов’язково те саме, щоint *
Маршалл Клоу

2
Це огидно, я думаю, що я зміню libstdc ++, щоб перешкодити йому працювати.
Джонатан
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.