Цель
В векторе x
, Я хотел бы вставить элементы вектора values
по индексам, хранящимся в векторе positions
. Обратите внимание, что вектор positions
не сортируется.
Например, из следующего ввода
std::vector<char> x = {'H','e','l','l','W','o','l','d'};
std::vector<char> values = {'r','o'};
std::vector<size_t> positions = {6,4};
Я ожидаю следующего вывода
std::vector<char> x = {'H','e','l','l','o','W','o','r','l','d'};
Моя реализация
Идея в первую очередь сортировать positions
и переупорядочить values
соответственно. Затем я изменяю размер вектора x
так что он может приветствовать новые элементы. Наконец, я перебираю вектор x
от его последнего индекса до самой нижней позиции вставки, спрашивая на каждом шаге, какой элемент должен быть туда и перемещать этот элемент (происходит ли он из более низкого индекса в x
из от values
).
#include <iostream>
#include <vector>
#include <numeric>
//// Print a vector
template <typename T>
void print(std::vector<T>& x)
{
for (auto& e : x) std::cout << e << " ";
std::cout << "n";
}
//// Return the indices to inform on how to sort the inputted vector
template <typename T>
std::vector<uint32_t> sort_indices(const std::vector<T> &v)
{
// initialize original index locations
std::vector<uint32_t> idx(v.size());
std::iota(idx.begin(), idx.end(), 0);
// sort indexes based on comparing values in v
std::sort(idx.begin(), idx.end(),
[&v](uint32_t i1, uint32_t i2) {return v[i1] < v[i2];});
return idx;
}
//// reorder vector based on indices
template <typename T>
void reorder(std::vector<T>& v, std::vector<uint32_t>& order)
{
auto v2 = v;
for (uint32_t i = 0 ; i < v.size() ; i++)
{
v[i] = v2[order[i]];
}
}
//// Insert multiple elements at specified positions into vector
template<typename T>
void insertAtPositions(std::vector<T>& x, std::vector<T>& values, std::vector<size_t>& positions)
{
// assert values and positions are the same size
assert(values.size() == positions.size());
// Special case - values is empty
if (values.size() == 0) return;
// sort the values and the positions where those values should be inserted
auto indices = sort_indices(positions);
reorder(positions, indices);
reorder(values, indices);
// Special case - x is empty
if (x.size() == 0)
{
x.swap(values);
return;
}
// Allocate memory to x
x.resize(x.size() + values.size());
// Move things to make room for insertions and insert
int pos_index = positions.size()-1;
for (size_t i = x.size()-1 ; pos_index >= 0 ; --i)
{
if (i == positions[pos_index] + pos_index)
{
// A new value should go at index i
x[i] = std::move(values[pos_index]);
--pos_index;
} else
{
// The value from index 'i-pos_index-1' must go to index 'i'
x[i] = std::move(x[i-pos_index-1]);
}
}
}
int main()
{
std::vector<char> x = {'H','e','l','l','W','o','l','d'};
std::vector<char> values = {'r','o'};
std::vector<size_t> positions = {6,4};
print(x);
insertAtPositions(x,values,positions);
print(x);
}
Он печатает, как и ожидалось
H e l l W o l d
H e l l o W o r l d
Есть ли более быстрые (или лучшие в каком-то другом отношении) реализации?
Обратите внимание, что
- на практике,
x
размер обычно составляет от 0 до 10 ^ 7 иpositions
(иvalues
) размер обычно составляет от 0 до 50. - У меня есть веские причины использовать вектор, а не двухстороннюю очередь, несмотря на эту вставку.
1 ответ
Нам нужно включить
<algorithm>
дляstd::sort
и<cassert>
дляassert
.void print(std::vector<T> &x)
: Поскольку мы не изменяем вектор, это должно занятьconst&
(и используйтеconst&
внутри цикла for на основе диапазона).void reorder(std::vector<T> &v, std::vector<uint32_t> &order)
: Очередной раз,order
может бытьconst&
.Мы должны использовать
std::size_t
для индексов в вектор, а неuint32_t
или жеsize_t
.Если нам нужен подписанный тип для работы с различиями индексов, мы должны использовать
std::ptrdiff_t
, нетint
. Возможно, будет проще посчитать вперед, а затем вычислить нужные нам индексы внутри цикла.Нет смысла в
std::move
ing achar
.
Вместо того, чтобы вставлять элементы на место, мы можем создать новый вектор и скопировать элементы, выбирая из соответствующего источника для каждого индекса. Это не требует больше памяти, чем вызов x.resize()
.
например:
{
std::vector<char> x = {'H', 'e', 'l', 'l', 'W', 'o', 'l', 'd'};
std::vector<char> values = {'r', 'o'};
std::vector<size_t> positions = {6, 4};
auto indices = sort_indices(positions);
reorder(positions, indices);
reorder(values, indices);
auto size = x.size() + values.size();
auto y = std::vector<char>();
y.reserve(size);
auto v = std::size_t{ 0 }; // index into x
auto p = std::size_t{ 0 }; // index into positions / values
for (auto i = std::size_t{ 0 }; i != size; ) // index into y
{
if (v == positions[p])
{
y.push_back(values[p]);
++p;
++i;
}
else
{
y.push_back(x[v]);
++v;
++i;
}
}
print(y);
}
Поскольку мы ожидаем x
вектор быть намного больше, чем values
для вставки, мы могли бы скопировать большие диапазоны из x
в else
ветка выше, что-то вроде:
auto beg = v;
auto end = (p == positions.size() ? x.size() : positions[p]);
y.insert(y.end(), x.begin() + beg, x.begin() + end);
v = end;
i += (beg - end);
(Предупреждение: код не протестирован должным образом).
«Для этого не требуется больше памяти, чем для звонка
x.resize()
. «В целом это не так. Особенно с данными типичными размерами эти несколько дополнительных записей могут очень часто не приводить к перераспределению. (Конечно, учитывая, что нетshrink_to_fit
был вызван до или последнее перераспределение произошло из-за вызоваresize
.) И оstd::move
ing achar
: Функция реализована в виде шаблона.— BlameTheBits