C ++, скопировать в вектор

146

Мне нужно скопировать std::setв std::vector:

std::set <double> input;
input.insert(5);
input.insert(6);

std::vector <double> output;
std::copy(input.begin(), input.end(), output.begin()); //Error: Vector iterator not dereferencable

В чем проблема?

CrocodileDundee
источник
5
есть также assign()функция:output.assign(input.begin(), input.end());
Джин Бушуев
ваш вектор пуст Существует множество способов исправить это, хотя люди указывают ниже.
AJG85
@Gene: assign () хочет зарезервировать () необходимый объем памяти заранее. Он будет использовать входные итераторы, чтобы определить, сколько нужно, если только итераторы не являются строго InputIterator, в этом случае он пропустит резервирование и приведет к перераспределению при каждом push_back (). На противоположном конце спектра двунаправленные итераторы позволят ему просто вычесть конец - начало. Однако итераторы std :: set не являются ни тем, ни другим (это ForwardIterator), и это прискорбно: в этом случае метод assign () просто обходит весь набор, чтобы определить его размер - плохая производительность на больших наборах.
Сергей Шевченко

Ответы:

213

Вам необходимо использовать back_inserter:

std::copy(input.begin(), input.end(), std::back_inserter(output));

std::copyне добавляет элементы в контейнер, в который вы вставляете: он не может; в контейнере есть только итератор. Из-за этого, если вы передаете выходной итератор напрямую std::copy, вы должны убедиться, что он указывает на диапазон, по крайней мере достаточно большой, чтобы содержать входной диапазон.

std::back_inserterсоздает выходной итератор, который вызывает push_backконтейнер для каждого элемента, поэтому каждый элемент вставляется в контейнер. В качестве альтернативы, вы могли бы создать достаточное количество элементов в, std::vectorчтобы содержать копируемый диапазон:

std::vector<double> output(input.size());
std::copy(input.begin(), input.end(), output.begin());

Или вы можете использовать std::vectorконструктор диапазона:

std::vector<double> output(input.begin(), input.end()); 
Джеймс МакНеллис
источник
3
Привет Джеймс, вместо твоей строки std :: copy (первый блок кода в твоем ответе), я не могу просто сделать output.insert(output.end(), input.begin(), input.end());вместо этого?
user2015453
или просто используйте версию cbegin и cend: output.insert(output.cend(), input.cbegin(), input.cend());что вы думаете? Спасибо.
user2015453
2
Должен ли я output.reserve (input.size ()); сам или я могу надеяться, что какой-то компилятор сделает это для меня?
jimifiki
@jimifiki, надеюсь, я не боюсь.
Алексис Вилке
Ваша первая векторная инициализация неверна. Вы создаете массив input,size()пустых записей и затем добавляете добавления после этого. Я думаю, что вы хотите использовать std::vector<double> output; output.reserve(input.size()); std::copy(...);.
Алексис
121

Просто используйте конструктор для вектора, который принимает итераторы:

std::set<T> s;

//...

std::vector v( s.begin(), s.end() );

Предполагается, что вам просто нужно содержимое s в v, и в v нет ничего до копирования данных в него.

Иаков
источник
42

Вот еще один вариант использования vector::assign:

theVector.assign(theSet.begin(), theSet.end());
TeddyC
источник
24

Вы не зарезервировали достаточно места в вашем векторном объекте для хранения содержимого вашего набора.

std::vector<double> output(input.size());
std::copy(input.begin(), input.end(), output.begin());
Marlon
источник
1
Это не заслуживает -1. В частности, это позволяет вектору выполнять только одно выделение (поскольку он не может определить расстояние итераторов множества в O (1)), и, если не было задано значение вектора для обнуления каждого элемента при построении, это может стоило бы позволить копии свести к memcpy. Последнее все еще может быть полезным, если реализация выяснит, что цикл в ctor вектора может быть удален. Конечно, первое также может быть достигнуто с резервом.
Фред Нурк
Я не знаю. Позвольте мне помочь вам с этим.
Вильгельмтель
Я дал тебе -1, но это было думо с моей стороны. Сделайте небольшое изменение, чтобы я мог отменить свой голос, и я дам вам +1: это на самом деле очень чистое решение из-за свойства fail-first.
Фред Фу
Я только что понял, что, если я отредактирую ответ сам, я смогу поднять голос. Сделал это, дал вам +1 для выделения памяти в первую очередь. Сожалею!
Фред Фу
3

Я думаю, что самый эффективный способ - это предварительно выделить, а затем использовать элементы:

template <typename T>
std::vector<T> VectorFromSet(const std::set<T>& from)
{
    std::vector<T> to;
    to.reserve(from.size());

    for (auto const& value : from)
        to.emplace_back(value);

    return to;
}

Таким образом, мы будем вызывать конструктор копирования только для каждого элемента, а не вызывать конструктор по умолчанию, а затем копировать оператор присваивания для других решений, перечисленных выше. Больше разъяснений ниже.

  1. back_inserter может использоваться, но он вызовет push_back () для вектора ( https://en.cppreference.com/w/cpp/iterator/back_insert_iterator ). emplace_back () более эффективен, потому что он избегает создания временного при использовании push_back () . Это не проблема с тривиально сконструированными типами, но будет влиять на производительность для нетривиально сконструированных типов (например, std :: string).

  2. Нам нужно избегать создания вектора с аргументом размера, который приводит к созданию всех элементов по умолчанию (ни за что). Как, например, с решением с использованием std :: copy () .

  3. И, наконец, метод vector :: assign () или конструктор, принимающий диапазон итераторов, не являются хорошими вариантами, потому что они будут вызывать std :: distance () (чтобы узнать количество элементов) на итераторах множества . Это приведет к нежелательной дополнительной итерации по всем элементам набора, поскольку набор является структурой данных дерева двоичного поиска и не реализует итераторы с произвольным доступом.

Надеюсь, это поможет.

dshvets1
источник
пожалуйста, добавьте ссылку на авторитет, почему это быстро, и что-то вроде того, почему back_inserterне нужно использовать
Тарик Веллинг
Добавил больше уточнений в ответ.
dshvets1
1

std::copyне может быть использован для вставки в пустой контейнер. Для этого вам нужно использовать insert_iterator следующим образом:

std::set<double> input;
input.insert(5);
input.insert(6);

std::vector<double> output;
std::copy(input.begin(), input.end(), inserter(output, output.begin())); 
Брэдли Суэйн
источник
3
Это не удается при первом перераспределении вектора: итератор из output.begin () становится недействительным.
Фред Нурк