Вставить несколько значений в вектор

У меня есть переменная std::vector<T>. У меня также есть две переменные типа T, первая из которых представляет значение в векторе, после которого я должен вставить, а вторая представляет значение для вставки.

Допустим, у меня есть этот контейнер: 1,2,1,1,2,2

И эти два значения равны 2 и 3 по отношению к их определениям выше. Затем я хочу написать функцию, которая будет обновлять контейнер, чтобы он содержал:

1,2,3,1,1,2,3,2,3

Я использую С++ 98 и Boost. Какие стандартные или повышающие функции я могу использовать для реализации этой функции?

Итерация по вектору и использование std::insert — это один из способов, но он становится беспорядочным, когда вы понимаете, что вам нужно не забыть перепрыгнуть через значение, которое вы только что вставили.


person Baz    schedule 15.08.2013    source источник
comment
Вы знаете о проблемах эффективности такого алгоритма с std::vector? Каждый раз, когда вы вставляете значение, копируется остальная часть вектора. Поскольку std::list не может быть хорошим решением, я могу придумать алгоритм на месте, который перемещает элемент только один раз. Но я не знаю, существует ли он в Boost.   -  person Pierre T.    schedule 15.08.2013
comment
Я не говорил о перераспределении вектора. Между элементами нет лишних пробелов, одной из основных характеристик вектора является наличие смежных данных. Конечно, если вы всегда вставляете в конце, проблемы не возникает. Что ж, посмотрите мой ответ для алгоритма, который работает на месте.   -  person Pierre T.    schedule 15.08.2013
comment
@PierreT.: Извините, я не понял, к чему вы клоните. Да, вы хотите сделать это, как показано в ответе Бенджамина Линдли: поместите данные в новый вектор, чтобы вы всегда добавляли данные в конце, а затем замените содержимое обратно в исходный вектор.   -  person Jerry Coffin    schedule 15.08.2013
comment
@JerryCoffin: Ну, как я уже сказал, у меня есть решение, которое не использует двойную память и копирует элементы только один раз (как в ответе Бенджамина).   -  person Pierre T.    schedule 15.08.2013
comment
@PierreT.: Если вы имеете в виду использование std::list, то вы в чем-то правы: он использует не удвоенный объем памяти, а тройной объем памяти. Каждый узел содержит два указателя в дополнение к фактическим данным, и в типичном случае все три имеют одинаковый размер.   -  person Jerry Coffin    schedule 15.08.2013
comment
@JerryCoffin Мой ответ невидим или вы просто не хотите на него смотреть?   -  person Pierre T.    schedule 15.08.2013
comment
@WhozCraig Вы удалили свой ответ, прежде чем я смог ответить. Да, я говорил о v.end(). См. что происходит с gcc.   -  person jrok    schedule 15.08.2013


Ответы (4)


Вот что я бы, вероятно, сделал:

vector<T> copy;
for (vector<T>::iterator i=original.begin(); i!=original.end(); ++i)
{
    copy.push_back(*i);
    if (*i == first)
        copy.push_back(second);
}
original.swap(copy);

Поместите вызов, чтобы зарезервировать там, если хотите. Вы знаете, что вам нужно место как минимум для original.size() элементов. Вы также можете выполнить начальную итерацию по вектору (или использовать std::count), чтобы определить точное количество элементов для резервирования, но без тестирования я не знаю, улучшит ли это производительность.

person Benjamin Lindley    schedule 15.08.2013
comment
Я бы reserve() первоначальный объем памяти, но в остальном это выглядит хорошо. Если бы мне требовалась производительность, я бы дополнительно попытался поменять местами/переместить элементы из одного контейнера в другой. - person Ulrich Eckhardt; 15.08.2013

Я предлагаю решение, которое работает на месте и за O(n) памяти и O(2n) времени. Вместо O(n^2) по времени решением, предложенным Лаэтнесом, и O(2n) по памяти решением, предложенным Бенджамином.

// First pass, count elements equal to first.
std::size_t elems = std::count(data.begin(), data.end(), first);
// Resize so we'll add without reallocating the elements.
data.resize(data.size() + elems);
vector<T>::reverse_iterator end = data.rbegin() + elems;
// Iterate from the end. Move elements from the end to the new end (and so elements to insert will have some place).
for(vector<T>::reverse_iterator new_end = data.rbegin(); end != data.rend() && elems > 0; ++new_end,++end)
{
  // If the current element is the one we search, insert second first. (We iterate from the end).
  if(*end == first)
  {
    *new_end = second;
    ++new_end;
    --elems;
  }
  // Copy the data to the end.
  *new_end = *end;
}

Этот алгоритм может содержать ошибки, но идея состоит в том, чтобы скопировать каждый элемент только один раз:

  1. Сначала подсчитайте, сколько элементов нам нужно вставить.
  2. Во-вторых, просматривая данные с конца и перемещая каждый элемент в новый конец.
person Pierre T.    schedule 15.08.2013
comment
data.resize(data.size() + элементы); не лучше векторного результата; результат.зарезервировать(...); ... оригинал.своп(результат). Вычисление результирующего размера, во-первых, хорошо, я думаю - person ; 15.08.2013
comment
И почему бы нет? Использую половину памяти. Если оригинал представляет собой вектор размером 10 МБ, я буду использовать только 10 МБ. Если вы используете временный вектор, вы будете использовать 20 МБ. - person Pierre T.; 15.08.2013
comment
@ Пьер Т.: C++ не имеет realloc - person ; 15.08.2013
comment
Это зависит. Если исходный вектор имеет достаточную емкость, дополнительное пространство не потребуется. Если это не так, то для вызова resize потребуется достаточно места, чтобы сохранить исходный буфер живым, пока он копирует элементы в новый буфер. В моем алгоритме всегда требуется дополнительное пространство. - person Benjamin Lindley; 15.08.2013
comment
@DieterLücking, вы имеете в виду, что внутри реализации std::vector не используется какой-либо механизм realloc? - person Pierre T.; 15.08.2013
comment
Если этот resize() изменит вектор capacity(), он сделает недействительным end итератор, который вы только что сохранили. Этот код не является хорошим. - person Blastfurnace; 15.08.2013

Вот что я, вероятно, сделал бы:

typedef ::std::vector<int> MyList;
typedef MyList::iterator MyListIter;

MyList data;

// ... fill data ...

const int searchValue = 2;
const int addValue = 3;

// Find first occurence of searched value
MyListIter iter = ::std::find(data.begin(), data.end(), searchValue);

while(iter != data.end())
{
    // We want to add our value after searched one
    ++iter;

    // Insert value and return iterator pointing to the inserted position
    // (original iterator is invalid now).
    iter = data.insert(iter, addValue);

    // This is needed only if we want to be sure that out value won't be used
    // - for example if searchValue == addValue is true, code would create
    // infinite loop.
    ++iter;

    // Search for next value.
    iter = ::std::find(iter, data.end(), searchValue);
}

но, как видите, я не мог избежать упомянутого вами приращения. Но я не думаю, что это было бы плохо: я бы поместил этот код в отдельные функции (вероятно, в какой-то модуль «core/utils») и, конечно же, реализовал бы эту функцию как шаблон, поэтому я бы написал только один раз - ИМХО приемлемо беспокоиться об увеличении значения только один раз. Очень приемлемо.

template <class ValueType>
void insertAfter(::std::vector<ValueType> &io_data,
                 const ValueType &i_searchValue,
                 const ValueType &i_insertAfterValue);

а то и лучше (ИМХО)

template <class ListType, class ValueType>
void insertAfter(ListType &io_data,
                 const ValueType &i_searchValue,
                 const ValueType &i_insertAfterValue);

ИЗМЕНИТЬ:

ну, я бы решил проблему немного по-другому: сначала подсчитать количество вхождений искомого значения (предпочтительно хранить в каком-то кеше, который можно хранить и использовать многократно), чтобы я мог подготовить массив раньше (только одно выделение) и использовать memcpy для перемещения исходные значения (конечно, только для таких типов, как int) или memmove (если размер выделенного вектора уже достаточен).

person Community    schedule 15.08.2013
comment
@DieterLücking: Ой, я недостаточно внимательно прочитал. Я отказываюсь от своего предыдущего комментария - он был просто неверным. - person Jerry Coffin; 15.08.2013
comment
Решение Бенджамина Линдли лучше для вектора, ваше лучше для списка. Следовательно, оба шаблона реализуют разные стратегии. - person ; 15.08.2013

Вместо этого O(1) дополнительной памяти и O(n) времени ):

template <typename T, typename A>
void do_thing(std::vector<T, A>& vec, T target, T inserted) {
    using std::swap;

    typedef typename std::vector<T, A>::size_type size_t;
    const size_t occurrences = std::count(vec.begin(), vec.end(), target);
    if (occurrences == 0) return;

    const size_t original_size = vec.size();
    vec.resize(original_size + occurrences, inserted);

    for(size_t i = original_size - 1, end = i + occurrences; i > 0; --i, --end) {
        if (vec[i] == target) {
            --end;
        }
        swap(vec[i], vec[end]);
    }
}
person Casey    schedule 15.08.2013