Apa cara terbaik untuk menggabungkan dua vektor?


189

Saya menggunakan multitreading dan ingin menggabungkan hasilnya. Sebagai contoh:

std::vector<int> A;
std::vector<int> B;
std::vector<int> AB;

Saya ingin AB harus memiliki isi A dan isi B dalam urutan itu. Apa cara paling efisien untuk melakukan sesuatu seperti ini?


1
Jika mencari efisiensi ketika Anda bekerja dengan wadah ukuran besar, mungkin akan lebih efisien untuk menggunakan daftar, di mana Anda dapat memisahkan satu dengan yang lainnya dengan beberapa operasi penunjuk. Tetapi daftar memiliki overhead ruang (pertimbangkan untuk menggunakan daftar tertaut tunggal).
Kemin Zhou

Jawaban:


322
AB.reserve( A.size() + B.size() ); // preallocate memory
AB.insert( AB.end(), A.begin(), A.end() );
AB.insert( AB.end(), B.begin(), B.end() );

6
Terima kasih! Tidak akan memikirkan cadangan.
jmasterx

10
harus menyalin setiap elemen, jadi itu O (n)
Kirill V. Lyadvinsky

1
Tidak yakin apakah akan mengajukan pertanyaan baru atau tidak, tetapi bisakah jawaban ini ditingkatkan saat mempertimbangkan pemindahan semantik? Apakah ada cara saya dapat mengharapkan / menginstruksikan kompiler untuk melakukan satu memori bergerak daripada perulangan semua elemen?
Broes De Cat

2
@boycy No. Ini adalah waktu konstan diamortisasi untuk push_back satu elemen. Untuk mendorong kembali n elemen adalah O (n)
Konrad Lindenbach

1
@ Konrad saya tidak menyiratkan sebaliknya, tapi terima kasih atas klarifikasi. Perhatikan kompleksitas operasi penyisipan tidak pernah diberikan dalam hal jumlah elemen yang dimasukkan - yang akan selalu memberikan O (n) - tetapi dalam hal jumlah elemen yang sudah ada dalam wadah, karena itu memberikan ukuran skalabilitasnya .
boycy

65

Ini adalah tepat apa fungsi anggota std::vector::insertadalah untuk

std::vector<int> AB = A;
AB.insert(AB.end(), B.begin(), B.end());

4
@Nick: Lambat dibandingkan dengan apa?
GManNickG

2
Mungkin itu memeriksa ruang yang cukup pada setiap sisipan elemen? Menggunakan cadangan sebelumnya akan mempercepatnya.
Rvdk

10
@Nick: Saya tidak akan terkejut jika setiap implementasi stdlib modern mengkhususkan insertpada iterator akses acak dan dicadangkan di muka.
GManNickG

1
@ Man: Itu poin yang adil karena kita tahu bahwa sumbernya juga vektor (di mana iterator distancememiliki kompleksitas O (1)). Namun, jaminan kinerja insertadalah sesuatu yang harus diperhatikan ketika Anda sering dapat melakukan lebih baik dengan perencanaan ke depan.
Nick Bastin

2
@RvdK memeriksa ruang hanya beberapa instruksi: kapasitas muat, bandingkan dengan ukuran, lompatan bersyarat; yang semuanya dapat diabaikan untuk sebagian besar kasus. Karena size < capacitysebagian besar waktu, prediksi cabang kemungkinan akan menyebabkan instruksi cabang yang tidak merealokasi ke dalam pipa instruksi, meminimalkan latensi yang diinduksi cabang kecuali untuk jumlah iterasi yang rendah. Ini mengasumsikan implementasi vektor yang baik, ditambah pipa instruksi CPU & prediksi cabang [baik], tetapi itu adalah asumsi yang cukup andal untuk toolchain modern dan mesin desktop. Tidak tahu tentang smartphone ..
boycy

27

Tergantung pada apakah Anda benar-benar perlu menyatukan dua vektor secara fisik atau Anda ingin memberikan tampilan penggabungan demi iterasi. Fungsi boost :: join

http://www.boost.org/doc/libs/1_43_0/libs/range/doc/html/range/reference/utilities/join.html

akan memberimu ini.

std::vector<int> v0;
v0.push_back(1);
v0.push_back(2);
v0.push_back(3);

std::vector<int> v1;
v1.push_back(4);
v1.push_back(5);
v1.push_back(6);
...

BOOST_FOREACH(const int & i, boost::join(v0, v1)){
    cout << i << endl;
}

harus memberi Anda

1
2
3
4
5
6

Catatan boost :: join tidak menyalin dua vektor ke wadah baru tetapi menghasilkan sepasang iterator (rentang) yang mencakup rentang kedua wadah. Akan ada beberapa overhead kinerja tetapi mungkin kurang dari itu menyalin semua data ke wadah baru terlebih dahulu.


1
Ide bagus. Setelah berpikir sebentar saya menyadari bahwa tujuan ini juga dapat dicapai tanpa menggunakan boost library. Saya telah memposting jawaban yang menjelaskan caranya.
Ronald Souza

11

Berdasarkan jawaban Kiril V. Lyadvinsky , saya membuat versi baru. Cuplikan ini menggunakan templat dan kelebihan beban. Dengan itu, Anda dapat menulis vector3 = vector1 + vector2dan vector4 += vector3. Semoga bisa membantu.

template <typename T>
std::vector<T> operator+(const std::vector<T> &A, const std::vector<T> &B)
{
    std::vector<T> AB;
    AB.reserve(A.size() + B.size());                // preallocate memory
    AB.insert(AB.end(), A.begin(), A.end());        // add A;
    AB.insert(AB.end(), B.begin(), B.end());        // add B;
    return AB;
}

template <typename T>
std::vector<T> &operator+=(std::vector<T> &A, const std::vector<T> &B)
{
    A.reserve(A.size() + B.size());                // preallocate memory without erase original data
    A.insert(A.end(), B.begin(), B.end());         // add B;
    return A;                                        // here A could be named AB
}

1
Apakah Anda bermaksud menambahkan elemen dari masing-masing vektor ke satu sama lain? Atau Anda bermaksud menambahkan? Ini jelas sekarang tetapi untuk 5 tahun ke depan ..? Anda tidak boleh membebani operator secara berlebihan jika artinya ambigu.
SR

2
@ SR maksud saya untuk concate. Saya menulis jawaban ini 3 tahun yang lalu. Saya masih tahu apa artinya. Tidak masalah di sana. Jika C ++ bisa memberikan kelebihannya sendiri, itu akan menjadi lebih baik. (dan ya ::diambil;)
aloisdg pindah ke codidact.com

Jelas tidak jelas secara umum yang v1 + v2tidak mewakili penambahan.
Apollys mendukung Monica


Alternatif akan digunakan @seperti pada F #
aloisdg pindah ke codidact.com

6

Dalam arah jawaban Bradgonesurfing, berkali-kali seseorang tidak perlu menggabungkan dua vektor (O (n)), tetapi hanya bekerja dengan mereka seolah-olah mereka digabungkan (O (1)) . Jika ini adalah kasus Anda, itu dapat dilakukan tanpa perlu meningkatkan perpustakaan.

Triknya adalah membuat proksi vektor: kelas pembungkus yang memanipulasi referensi ke kedua vektor, secara eksternal dilihat sebagai satu, yang berdekatan.

PEMAKAIAN

std::vector<int> A{ 1, 2, 3, 4, 5};
std::vector<int> B{ 10, 20, 30 };

VecProxy<int> AB(A, B);  // ----> O(1). No copies performed.

for (size_t i = 0; i < AB.size(); ++i)
    std::cout << AB[i] << " ";  // 1 2 3 4 5 10 20 30

PENERAPAN

template <class T>
class VecProxy {
private:
    std::vector<T>& v1, v2;
public:
    VecProxy(std::vector<T>& ref1, std::vector<T>& ref2) : v1(ref1), v2(ref2) {}
    const T& operator[](const size_t& i) const;
    const size_t size() const;
};

template <class T>
const T& VecProxy<T>::operator[](const size_t& i) const{
    return (i < v1.size()) ? v1[i] : v2[i - v1.size()];
};

template <class T>
const size_t VecProxy<T>::size() const { return v1.size() + v2.size(); };

MANFAAT UTAMA

Ini O (1) (waktu konstan) untuk membuatnya, dan dengan alokasi memori ekstra minimal.

BEBERAPA STUFF UNTUK MEMPERTIMBANGKAN

  • Anda hanya harus melakukannya jika Anda benar-benar tahu apa yang Anda lakukan ketika berhadapan dengan referensi . Solusi ini dimaksudkan untuk tujuan khusus dari pertanyaan yang dibuat, yang mana ia bekerja dengan cukup baik . Menggunakannya dalam konteks lain apa pun dapat menyebabkan perilaku yang tidak terduga jika Anda tidak yakin tentang cara kerja referensi.
  • Dalam contoh ini, AB tidak menyediakan operator akses non-const ([]). Jangan ragu untuk memasukkannya, tetapi perlu diingat: karena AB berisi referensi, untuk menetapkan nilai-nilai itu juga akan mempengaruhi elemen asli dalam A dan / atau B. Apakah ini fitur yang diinginkan atau tidak, ini adalah pertanyaan khusus aplikasi yang harus hati-hati mempertimbangkan.
  • Setiap perubahan yang dilakukan langsung ke A atau B (seperti menetapkan nilai, pengurutan, dll.) Juga akan "memodifikasi" AB. Ini tidak selalu buruk (sebenarnya, itu bisa sangat berguna: AB tidak pernah perlu diperbarui secara eksplisit untuk menjaga dirinya disinkronkan dengan A dan B), tetapi itu tentu saja perilaku yang harus diperhatikan. Pengecualian penting: mengubah ukuran A dan / atau B menjadi lebih besar dapat menyebabkan ini dialokasikan kembali dalam memori (untuk kebutuhan ruang yang berdekatan), dan ini pada gilirannya akan membatalkan AB.
  • Karena setiap akses ke elemen didahului oleh tes (yaitu, "i <v1.size ()"), waktu akses VecProxy, meskipun konstan, juga sedikit lebih lambat daripada vektor.
  • Pendekatan ini dapat digeneralisasikan ke n vektor. Saya belum mencoba, tetapi itu seharusnya tidak menjadi masalah besar.

2

Satu lagi varian sederhana yang belum disebutkan:

copy(A.begin(),A.end(),std::back_inserter(AB));
copy(B.begin(),B.end(),std::back_inserter(AB));

Dan menggunakan algoritma gabungan:

#include <algorithm> #include <vector> #include <iterator> #include <iostream> #include <sstream> #include <string> template<template<typename, typename...> class Container, class T> std::string toString(const Container<T>& v) { std::stringstream ss; std::copy(v.begin(), v.end(), std::ostream_iterator<T>(ss, "")); return ss.str(); }; int main() { std::vector<int> A(10); std::vector<int> B(5); //zero filled std::vector<int> AB(15); std::for_each(A.begin(), A.end(), [](int& f)->void { f = rand() % 100; }); std::cout << "before merge: " << toString(A) << "\n"; std::cout << "before merge: " << toString(B) << "\n"; merge(B.begin(),B.end(), begin(A), end(A), AB.begin(), [](int&,int&)->bool {}); std::cout << "after merge: " << toString(AB) << "\n"; return 1; }


-1

Jika vektor Anda diurutkan *, periksa set_union dari <algorithm>.

set_union(A.begin(), A.end(), B.begin(), B.end(), AB.begin());

Ada contoh yang lebih teliti di tautan

* terima kasih rlbond


4
juga, ia tidak melakukan hal yang sama dengan menambahkan lurus - elemen-elemen dalam kisaran output unik, yang mungkin bukan apa yang diinginkan OP (mereka bahkan mungkin tidak sebanding). Ini tentu bukan cara yang paling efisien untuk melakukannya.
Peter

-1

Semua solusi sudah benar, tetapi saya merasa lebih mudah hanya menulis fungsi untuk mengimplementasikan ini. seperti ini:

template <class T1, class T2>
void ContainerInsert(T1 t1, T2 t2)
{
    t1->insert(t1->end(), t2->begin(), t2->end());
}

Dengan begitu Anda dapat menghindari penempatan sementara seperti ini:

ContainerInsert(vec, GetSomeVector());
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.