C ++, salin set ke vektor

146

Saya perlu menyalin std::setke 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

Dimana masalahnya?

CrocodileDundee
sumber
5
ada juga assign()fungsi:output.assign(input.begin(), input.end());
Gene Bushuyev
vektor Anda kosong. Ada banyak cara untuk memperbaiki itu meskipun orang menunjukkan di bawah ini.
AJG85
@ Gen: assign () ingin memesan () jumlah penyimpanan yang diperlukan sebelumnya. Ini akan menggunakan input iterator untuk menentukan berapa yang dibutuhkan, kecuali iteratornya benar-benar InputIterator, dalam hal ini akan melewati reservasi dan menghasilkan realokasi pada setiap push_back (). Di ujung lain dari spektrum, BiderectionalIterators akan memungkinkannya untuk mengurangi akhir saja. std :: set iterators, bagaimanapun, tidak (mereka adalah ForwardIterator), dan itu sangat disayangkan: dalam hal ini, assign () hanya akan berjalan di seluruh set untuk menentukan ukurannya - kinerja buruk pada set besar.
Sergey Shevchenko

Jawaban:

213

Anda perlu menggunakan back_inserter:

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

std::copytidak menambahkan elemen ke wadah yang Anda masukkan: tidak bisa; hanya memiliki iterator ke dalam wadah. Karena itu, jika Anda meneruskan iterator keluaran langsung ke std::copy, Anda harus memastikan itu menunjuk ke kisaran yang setidaknya cukup besar untuk menahan rentang input.

std::back_insertermembuat iterator keluaran yang memanggil push_backwadah untuk setiap elemen, sehingga setiap elemen dimasukkan ke dalam wadah. Atau, Anda bisa membuat sejumlah elemen dalam std::vectoruntuk menahan rentang yang disalin:

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

Atau, Anda bisa menggunakan std::vectorkonstruktor rentang:

std::vector<double> output(input.begin(), input.end()); 
James McNellis
sumber
3
Hai James, alih-alih baris std :: copy Anda (blok kode pertama dalam jawaban Anda), tidak bisakah saya melakukannya output.insert(output.end(), input.begin(), input.end());?
user2015453
atau hanya menggunakan versi cbegin dan cend: output.insert(output.cend(), input.cbegin(), input.cend());Bagaimana menurut Anda? Terima kasih.
user2015453
2
Haruskah saya output.reserve (input.size ()); sendiri atau bisakah saya berharap bahwa beberapa kompiler melakukannya untuk saya?
jimifiki
@jimifiki, jangan harap aku takut.
Alexis Wilke
Inisialisasi vektor pertama Anda salah. Anda membuat array input,size()entri kosong dan kemudian menambahkan append setelah itu. Saya pikir Anda bermaksud menggunakannya std::vector<double> output; output.reserve(input.size()); std::copy(...);.
Alexis Wilke
121

Cukup gunakan konstruktor untuk vektor yang mengambil iterator:

std::set<T> s;

//...

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

Asumsikan Anda hanya ingin konten s di v, dan tidak ada di v sebelum menyalin data ke dalamnya.

Yakub
sumber
42

inilah alternatif lain menggunakan vector::assign:

theVector.assign(theSet.begin(), theSet.end());
TeddyC
sumber
24

Anda belum memesan ruang yang cukup di objek vektor untuk menyimpan konten set Anda.

std::vector<double> output(input.size());
std::copy(input.begin(), input.end(), output.begin());
Marlon
sumber
1
Ini tidak layak -1. Secara khusus, ini memungkinkan vektor untuk hanya melakukan satu alokasi (karena tidak dapat menentukan jarak set iterator di O (1)), dan, jika tidak didefinisikan untuk vektor untuk nol setiap elemen ketika dibangun, ini bisa bermanfaat untuk membiarkan salinan mendidih menjadi memcpy. Yang terakhir masih bisa bermanfaat jika angka implementasi loop di ctor vektor dapat dihapus. Tentu saja, yang pertama juga dapat dicapai dengan cadangan.
Fred Nurk
Saya tidak tahu Biarkan saya membantu Anda dengan itu.
wilhelmtell
Saya memberi Anda -1, tapi itu adalah bagian dari saya. Buat edit kecil agar saya dapat membatalkan suara saya, dan saya akan memberi Anda +1: ini sebenarnya solusi yang sangat bersih karena properti gagal-pertama.
Fred Foo
Saya baru saja mengetahui bahwa jika saya mengedit sendiri jawabannya, saya dapat melakukan upvote. Apakah itu, memberi Anda +1 untuk alokasi memori gagal-pertama. Maaf!
Fred Foo
3

Saya pikir cara yang paling efisien adalah dengan melakukan pra-alokasi dan kemudian menggunakan elemen:

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;
}

Dengan begitu kita hanya akan memanggil copy constructor untuk setiap elemen sebagai lawan memanggil constructor default terlebih dahulu dan kemudian menyalin operator penugasan untuk solusi lain yang tercantum di atas. Penjelasan lebih lanjut di bawah ini.

  1. back_inserter dapat digunakan tetapi akan memanggil push_back () pada vektor ( https://en.cppreference.com/w/cpp/iterator/back_insert_iterator ). emplace_back () lebih efisien karena ia menghindari membuat sementara ketika menggunakan push_back () . Ini bukan masalah dengan tipe yang dibangun secara sepele tetapi akan menjadi implikasi kinerja untuk tipe yang tidak dibangun dengan sepele (misalnya std :: string).

  2. Kita perlu menghindari membangun vektor dengan argumen ukuran yang menyebabkan semua elemen dibangun secara default (tanpa biaya). Seperti dengan solusi menggunakan std :: copy () , misalnya.

  3. Dan, akhirnya, metode vector :: assign () atau konstruktor yang mengambil rentang iterator bukanlah pilihan yang baik karena mereka akan memanggil std :: distance () (untuk mengetahui jumlah elemen) pada set iterators. Ini akan menyebabkan iterasi tambahan yang tidak diinginkan melalui semua elemen set karena set adalah struktur data Binary Search Tree dan tidak menerapkan iterator akses acak.

Semoga itu bisa membantu.

dshvets1
sumber
tolong tambahkan referensi ke otoritas mengapa ini cepat dan sesuatu seperti mengapa a back_insertertidak perlu digunakan
Tarick Welling
Menambahkan lebih banyak klarifikasi dalam jawabannya.
dshvets1
1

std::copytidak dapat digunakan untuk memasukkan ke dalam wadah kosong. Untuk melakukan itu, Anda perlu menggunakan insert_iterator seperti:

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

std::vector<double> output;
std::copy(input.begin(), input.end(), inserter(output, output.begin())); 
Bradley Swain
sumber
3
Gagal ini pertama kali vektor merealokasi: iterator dari output.begin () menjadi tidak valid.
Fred Nurk