Как стереть элемент из 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 или vector. Это правда, что удаление элемента из вектора может быть дорогостоящим - особенно если вектор большой, но нет оснований думать, что deque будет лучше, чем вектор из примера кода, который вы только что опубликовали.
Сова

6
Например, если у вас есть графическое приложение, в котором вы отображаете «список» вещей, в который вы вставляете / удаляете вещи в интерактивном режиме, подумайте, что вы просматриваете список 50-100 раз каждую секунду, чтобы отобразить их, и вы добавляете / удаляете вещи несколько раз. раз каждую минуту. Поэтому реализация «списка» как вектора, вероятно, является лучшим вариантом с точки зрения общей эффективности.
Мишель Бийо,

Ответы:


706

Чтобы удалить один элемент, вы можете сделать:

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]
K - Токсичность в SO растет.

2
Заранее вы должны сохранить итератор в переменной. Если вы используете std :: next, вы можете сделать это в одной строке: vec.erase (next (begin (vec), 123));
Дани

8
Спасибо всем, кто ответил. Что мы должны думать о дизайне класса, когда такая простая операция, как удаление элемента, требует, чтобы он пришел в StackOverflow?
Пьер

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

212

Метод erase в 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: Поскольку a vector<T>::iteratorявляется итератором с произвольным доступом, ваша версия в порядке и, возможно, немного понятнее. Но версия, которую выложил Макс, должна прекрасно работать, если вы измените контейнер на другой, который не поддерживает итераторы с произвольным доступом
Lily Ballard

2
Это лучший ответ, так как он применим и к другим форматам контейнеров. Вы также можете использовать std :: next ().
Бим

Намного лучший подход, так как он не зависит от внутренних элементов контейнера.
BartoszKP

std :: advance требуется только в том случае, если вы думаете, что это не будет вектор, то есть список. Но как вы указали здесь, оператор + не будет проще? Согласно этому 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). Код предполагает, что vector :: iterator неявно конструируется из необработанного указателя, что не требуется стандартом.
CuriousGeorge

1
Это особенно верно для отладочных итераторов
Нишант Сингх

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
Это полностью необходимо добавить в стандартную библиотеку, как unordered_removeи unordered_remove_if… если только это не было, и я пропустил это, что происходит все чаще и чаще в наши дни :)
Уилл Кроуфорд,

Если бы предложили использовать перемещение-назначение или своп вместо копирования-назначения.
Карстен С.

std::removeпереупорядочивает контейнер так, чтобы все элементы, которые будут удалены, были в конце, нет необходимости делать это вручную, как это, если вы используете C ++ 17.
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 в vec. Это довольно быстро. Для обработки 20 000 000 элементов этот алгоритм занимает всего 0,8 с!

Я сделал то же самое с методом стирания, и это занимает много-много времени:

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
как это отвечает на вопрос?
Регис Порталес

4
Интересно, но не актуально для вопроса!
Родди

Не будет ли алгоритм на месте быстрее?
user202729

2
это std :: remove_if (+
erase

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. Это может быть проблемой, если вы перебираете вектор, поскольку vector size () уменьшается. Если у вас есть проблема, подобная этой, предложенная ссылка предложила использовать существующий алгоритм в стандартной библиотеке C ++. и "удалить" или "удалить_if".

Надеюсь что это помогло


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.