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

Де проблема?


5
є також assign()функція:output.assign(input.begin(), input.end());
Гена Бушуєва

ваш вектор порожній. Існує безліч способів виправити це, хоча люди як вказують нижче.
AJG85

@Gene: призначити () хоче заздалегідь зарезервувати () необхідну кількість пам’яті. Він використовуватиме ітератори введення, щоб визначити, скільки потрібно, якщо тільки ітератори не є строго InputIterator, і в цьому випадку він пропустить резервування та призведе до перерозподілу на кожному push_back (). На протилежному кінці спектру BiderectionalIterators дозволить йому просто відняти кінець - почати. Однак ітератори std :: set не є жодними (вони ForwardIterator), і це прикро: у такому випадку призначення () просто пройде весь набір, щоб визначити його розмір - погана продуктивність на великих наборах.
Сергій Шевченко

Відповіді:


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());замість цього?
користувач2015453

або просто використовувати версію cbegin і cend: output.insert(output.cend(), input.cbegin(), input.cend());як ви думаєте? Дякую.
користувач2015453

2
Чи слід виводити.резервувати (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, і в ньому немає нічого, перш ніж копіювати дані до нього.


42

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

theVector.assign(theSet.begin(), theSet.end());

24

Ви не зарезервували достатньо місця у векторному об'єкті, щоб вмістити вміст вашого набору.

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

1
Цього не заслуговує -1. Зокрема, це дозволяє вектору виконувати лише одне розподілення (оскільки він не може визначити відстань встановлених ітераторів в O (1)), і, якщо для побудови вектора не було визначено нуль кожного елемента при його побудові, це може Варто дозволити копії звести до мемпи. Останнє все-таки може бути корисним, якщо реалізація з'ясує цикл у ctor вектора. Звичайно, колишнього можна досягти і з резервом.
Фред Нурк

Не знаю. Дозвольте мені допомогти вам у цьому.
wilhelmtell

Я дав вам -1, але це був тонкий з мого боку. Зробіть невелику редагування, щоб я міг відмінити свій голос, і я дам вам +1: це насправді дуже чисте рішення через властивість першої помилки.
Фред Фоо

Я тільки що з'ясував, що якщо я сам редагую відповідь, я можу зробити виклик. Це зробило вам +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. І, нарешті, метод :: :: (( конструктор), що приймає діапазон ітераторів, не є хорошими варіантами, оскільки вони будуть викликати std :: distance () (щоб знати кількість елементів) на встановлених ітераторах. Це спричинить небажану додаткову ітерацію через усі набір елементів, оскільки набір є структурою даних Binary Search Tree, і він не реалізує ітераторів випадкового доступу.

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


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

Додано більше пояснень у відповідь.
dshvets1

1

std::copyне можна використовувати для вставки в порожній контейнер. Для цього вам потрібно використовувати вставник_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 () стає недійсним.
Фред Нурк
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.