Cara terbaik untuk mengekstrak subvektor dari vektor?


295

Misalkan saya memiliki ukuran std::vector(sebut saja myVec) N. Apa cara paling sederhana untuk membuat vektor baru yang terdiri dari salinan elemen X hingga Y, di mana 0 <= X <= Y <= N-1? Misalnya myVec [100000]melalui myVec [100999]dalam ukuran vektor 150000.

Jika ini tidak dapat dilakukan secara efisien dengan vektor, apakah ada tipe data STL lain yang harus saya gunakan?


7
Anda mengatakan Anda ingin mengekstrak subvektor, tetapi menurut saya apa yang sebenarnya Anda inginkan adalah tampilan / akses ke subvektor - perbedaannya adalah bahwa pandangan tidak akan menyalin - sekolah tua C ++ akan menggunakan start pointer dan end pointer, mengingat fakta bahwa mem pada std :: vector bersebelahan, maka mungkin bagi Anda untuk beralih menggunakan pointer dan dengan demikian menghindari penyalinan, namun jika Anda tidak keberatan menyalin, maka inisialisasi saja vektor baru dengan ruang lingkup sebelumnya vektor
serup

Ada .data () ( cplusplus.com/reference/vector/vector/data ) sejak c ++ 11. Namun, menggunakan pointer tidak disarankan dalam wadah stl, lihat stackoverflow.com/questions/31663770/…
David Tóth

Jawaban:


371
vector<T>::const_iterator first = myVec.begin() + 100000;
vector<T>::const_iterator last = myVec.begin() + 101000;
vector<T> newVec(first, last);

Ini adalah operasi O (N) untuk membangun vektor baru, tetapi sebenarnya tidak ada cara yang lebih baik.


12
+1, juga O (YX), yang kurang dari atau sama dengan O (N) (dan dalam contohnya jauh lebih sedikit)
orip

74
@orip Yah, setelah itu O (N).
Johann Gerell

55
@Regregator: Tidak masuk akal untuk menggunakan notasi O besar di mana N adalah angka tertentu. Big-O mengkomunikasikan tingkat pertumbuhan sehubungan dengan bagaimana N berubah. Johann: Yang terbaik adalah tidak menggunakan satu nama variabel dalam dua cara. Kami biasanya mengatakan O(Y-X), atau mengatakan O(Z) where Z=Y-X.
Mooing Duck

2
@GregRogers Dengan menggunakan cara ini, kita perlu mendeklarasikan vektor baru. Apakah ada cara untuk mengubah vektor asli? sesuatu seperti myVec (pertama, terakhir)? Saya tahu ini salah, tetapi saya benar-benar membutuhkan solusinya karena saya ingin menggunakan rekursi dalam kode saya, dan perlu berulang kali menggunakan vektor yang sama (walaupun diubah). Terima kasih!
ulyssis2

13
Kenapa tidak adil vector<T> newVec(myVec.begin() + 100000, myVec.begin() + 101000);?
aquirdturtle

88

Cukup gunakan konstruktor vektor.

std::vector<int>   data();
// Load Z elements into data so that Z > Y > X

std::vector<int>   sub(&data[100000],&data[101000]);

2
Ok, saya tidak menyadari bahwa sesederhana itu untuk mendapatkan iterator dari elemen vektor sewenang-wenang.
An̲̳̳drew

5
Mengambil alamat dari elemen-elemen vektor adalah hack yang tidak dapat diport yang akan pecah jika penyimpanan vektor tidak bersebelahan. Gunakan begin () + 100000 etc.
j_random_hacker

2
Buruk saya, ternyata standar menjamin bahwa penyimpanan vektor berdekatan. Namun demikian itu praktik yang buruk untuk bekerja dengan alamat seperti ini karena tentu saja tidak dijamin untuk bekerja untuk semua kontainer yang mendukung akses acak, sementara begin () + 100000.
j_random_hacker

33
@j_random_hacker: Maaf harus tidak setuju. Spesifikasi STL untuk std :: vector secara eksplisit diubah untuk mendukung jenis prosedur ini. Pointer juga merupakan tipe iterator yang valid. Lihat iterator_traits <>
Martin York

6
@ taktak004 Tidak. Ingat bahwa operator[]mengembalikan referensi. Hanya pada titik di mana Anda membaca atau menulis referensi bahwa itu akan menjadi pelanggaran akses. Karena kami tidak melakukan keduanya tetapi sebaliknya mendapatkan alamat, kami belum meminta UB ,.
Martin York

28

std::vector<T>(input_iterator, input_iterator), dalam kasus Anda foo = std::vector<T>(myVec.begin () + 100000, myVec.begin () + 150000);, lihat misalnya di sini


1
Karena Andrew sedang mencoba membuat vektor baru, saya akan merekomendasikan "std :: vector foo (..." daripada menyalin dengan "foo = std :: vector (..."
Drew Dormann

4
Ya, tentu saja, tetapi apakah Anda mengetik std :: vector <int> foo = std :: vector (...) atau std :: vector <int> foo (...) tidak masalah.
Anteru

19

Hari ini, kami menggunakan spans! Jadi, Anda akan menulis:

#include <gsl/span>

...
auto start_pos = 100000;
auto length = 1000;
auto span_of_myvec = gsl::make_span(myvec);
auto my_subspan = span_of_myvec.subspan(start_pos, length);

untuk mendapatkan rentang 1000 elemen dari jenis yang sama dengan myvec's. Atau bentuk yang lebih singkat:

auto my_subspan = gsl::make_span(myvec).subspan(1000000, 1000);

(tapi saya tidak terlalu suka ini, karena arti dari setiap argumen numerik tidak sepenuhnya jelas; dan akan menjadi lebih buruk jika panjang dan start_pos memiliki urutan yang sama besarnya.)

Ngomong-ngomong, ingat bahwa ini bukan salinan, itu hanya tampilan data dalam vektor, jadi hati-hati. Jika Anda ingin salinan yang sebenarnya, Anda dapat melakukan:

std::vector<T> new_vec(my_subspan.cbegin(), my_subspan.cend());

Catatan:


akan menggunakan cbegindan cendhanya untuk prinsip;) std::cbegindll bahkan.
JHBonarius

1
@JHBonarius: Melihat bagaimana kode ini tidak bergantung pada pilihan kontainer, saya tidak melihat ada manfaat tertentu; soal rasa kurasa.
einpoklum

10

Jika keduanya tidak akan dimodifikasi (tidak ada penambahan / penghapusan item - memodifikasi yang sudah ada baik-baik saja selama Anda memperhatikan masalah threading), Anda dapat dengan mudah berkeliling data.begin() + 100000dan data.begin() + 101000, dan berpura-pura bahwa itu adalah begin()dan end()dari vektor yang lebih kecil.

Atau, karena penyimpanan vektor dijamin bersebelahan, Anda bisa dengan mudah memberikan 1000 item array:

T *arrayOfT = &data[0] + 100000;
size_t arrayOfTLength = 1000;

Kedua teknik ini membutuhkan waktu konstan, tetapi mengharuskan panjang data tidak meningkat, memicu realokasi.


Ini juga baik jika Anda ingin vektor asli dan subvektor dihubungkan.
PyRulez

7

Diskusi ini cukup lama, tetapi yang paling sederhana belum disebutkan, dengan inisialisasi daftar :

 vector<int> subvector = {big_vector.begin() + 3, big_vector.end() - 2}; 

Ini membutuhkan c ++ 11 atau lebih tinggi.

Contoh penggunaan:

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

int main(){

    vector<int> big_vector = {5,12,4,6,7,8,9,9,31,1,1,5,76,78,8};
    vector<int> subvector = {big_vector.begin() + 3, big_vector.end() - 2};

    cout << "Big vector: ";
    for_each(big_vector.begin(), big_vector.end(),[](int number){cout << number << ";";});
    cout << endl << "Subvector: ";
    for_each(subvector.begin(), subvector.end(),[](int number){cout << number << ";";});
    cout << endl;
}

Hasil:

Big vector: 5;12;4;6;7;8;9;9;31;1;1;5;76;78;8;
Subvector: 6;7;8;9;9;31;1;1;5;76;

6

Anda tidak menyebutkan tipe apa std::vector<...> myVec, tetapi jika itu tipe sederhana atau struct / kelas yang tidak menyertakan pointer, dan Anda menginginkan efisiensi terbaik, maka Anda dapat melakukan salinan memori langsung (yang saya pikir akan lebih cepat daripada jawaban lain disediakan). Berikut ini adalah contoh umum untuk std::vector<type> myVecdi mana typedalam hal ini adalah int:

typedef int type; //choose your custom type/struct/class
int iFirst = 100000; //first index to copy
int iLast = 101000; //last index + 1
int iLen = iLast - iFirst;
std::vector<type> newVec;
newVec.resize(iLen); //pre-allocate the space needed to write the data directly
memcpy(&newVec[0], &myVec[iFirst], iLen*sizeof(type)); //write directly to destination buffer from source buffer

2
Saya ingin tahu apakah dengan -O3, @ Anteru's "using constructor" std::vector(myVec.begin () + 100000, myVec.begin () + 150000);, bukankah versi yang lebih lama dari ini menghasilkan perakitan yang persis sama?
sandthorn

1
MSVC ++ 2015, misalnya, mengkompilasi std::vector<>(iter, iter)ke memmove(), jika sesuai (jika konstruktor sepele, untuk definisi sepele yang sesuai).
Pablo H

1
Jangan panggil memcpy. Apakah seorang std::copyatau konstruktor yang menerima rentang (dua iterator), dan kompiler dan std.library akan berkonspirasi untuk memanggil memcpybila perlu.
Bulletmagnet

4

Anda bisa menggunakannya insert

vector<type> myVec { n_elements };

vector<type> newVec;

newVec.insert(newVec.begin(), myVec.begin() + X, myVec.begin() + Y);

3

Anda dapat menggunakan salin STL dengan kinerja O (M) ketika M adalah ukuran subvektor.


Terangkat karena menunjuk saya ke arah yang benar tetapi saya dapat melihat mengapa @LokiAstari menyarankan itu bukan pilihan yang benar - karena STL :: copy berfungsi dengan dua std :: vector <T> array dengan ukuran dan jenis yang sama. Di sini, OP ingin menyalin subbagian ke dalam array baru yang lebih kecil seperti yang diuraikan di sini dalam posting OP: "0 <= X <= Y <= N-1"
Andrew

@ Andrew, lihat contoh menggunakan std :: copy and std ::
back_inserter

@LokiAstari kenapa tidak?
chrisg

2
@LokiAstari Saya merujuk pada pengeditan untuk hal ini yang tidak selamat dari peer review, yang menampilkan contoh <br/> vektor <T> newvec; std :: copy (myvec.begin () + 10000, myvec.begin () +10100, std :: back_inserter (newvec)); <br/> dalam hal ini, Anda tidak perlu membangun tujuan terlebih dahulu, tetapi tentu saja, inisialisasi langsung lebih ... langsung.
chrisg

1
@ Chrisg: Ini juga dua baris. Selain itu Anda harus tetap menggunakan baris ketiga untuk memastikannya efisien. newvec.reserve(10100 - 10000);. Ini jelas merupakan pilihan dan secara teknis itu akan berhasil. Tetapi dari dua yang akan Anda rekomendasikan?
Martin York

1

Satu-satunya cara untuk memproyeksikan koleksi yang bukan waktu linear adalah melakukannya dengan malas, di mana "vektor" yang dihasilkan sebenarnya adalah subtipe yang mendelegasikan ke koleksi asli. Sebagai contoh, List#subseqmetode Scala membuat sub-urutan dalam waktu yang konstan. Namun, ini hanya berfungsi jika koleksi tersebut tidak berubah dan jika bahasa yang mendasari olahraga pengumpulan sampah.


dalam c ++ cara untuk melakukan itu adalah memiliki vektor shared_ptr ke X daripada vektor X dan kemudian menyalin SP, tapi sayangnya saya tidak berpikir itu lebih cepat karena operasi atom terlibat dengan cpying SP. Atau vektor asli bisa menjadi const shared_ptr vektor sebagai gantinya dan Anda hanya mengambil referensi untuk mengatur ulang di dalamnya. ofc Anda tidak perlu membuatnya menjadi shared_ptr vektor tetapi kemudian Anda memiliki masalah seumur hidup ... semua ini di luar kepala saya, bisa salah ...
NoSenseEtAl

0

Posting ini terlambat hanya untuk orang lain .. Aku yakin koder pertama selesai sekarang. Untuk tipe data sederhana salinan tidak diperlukan, cukup kembali ke metode kode C lama yang baik.

std::vector <int>   myVec;
int *p;
// Add some data here and set start, then
p=myVec.data()+start;

Kemudian berikan pointer p dan len ke apa pun yang membutuhkan subvektor.

notelen pasti !! len < myVec.size()-start


Ini tidak melakukan salinan.
Trilarion

0

Mungkin array_view / span di pustaka GSL adalah pilihan yang baik.

Berikut ini juga implementasi file tunggal: array_view .


Mohon tambahkan jawaban di sini bersama tautan. Karena tautan eksternal mungkin berubah di masa mendatang
Panther

0

Menyalin elemen dari satu vektor ke yang lain dengan mudah
Dalam contoh ini, saya menggunakan vektor pasangan untuk membuatnya mudah dimengerti
`

vector<pair<int, int> > v(n);

//we want half of elements in vector a and another half in vector b
vector<pair<lli, lli> > a(v.begin(),v.begin()+n/2);
vector<pair<lli, lli> > b(v.begin()+n/2, v.end());


//if v = [(1, 2), (2, 3), (3, 4), (4, 5), (5, 6)]
//then a = [(1, 2), (2, 3)]
//and b = [(3, 4), (4, 5), (5, 6)]

//if v = [(1, 2), (2, 3), (3, 4), (4, 5), (5, 6), (6, 7)]
//then a = [(1, 2), (2, 3), (3, 4)]
//and b = [(4, 5), (5, 6), (6, 7)]

'
Seperti yang Anda lihat, Anda dapat dengan mudah menyalin elemen dari satu vektor ke yang lain, jika Anda ingin menyalin elemen dari indeks 10 hingga 16 misalnya maka kita akan menggunakan

vector<pair<int, int> > a(v.begin()+10, v.begin+16);

dan jika Anda ingin elemen dari indeks 10 ke beberapa indeks dari akhir, maka dalam hal itu

vector<pair<int, int> > a(v.begin()+10, v.end()-5);

Semoga ini bisa membantu, ingat saja dalam kasus terakhir v.end()-5 > v.begin()+10


0

Namun pilihan lain: Berguna misalnya ketika bergerak antara a thrust::device_vectordan a thrust::host_vector, di mana Anda tidak dapat menggunakan konstruktor.

std::vector<T> newVector;
newVector.reserve(1000);
std::copy_n(&vec[100000], 1000, std::back_inserter(newVector));

Seharusnya juga kompleksitas O (N)

Anda dapat menggabungkan ini dengan kode jawaban teratas

vector<T>::const_iterator first = myVec.begin() + 100000;
vector<T>::const_iterator last = myVec.begin() + 101000;
std::copy(first, last, std::back_inserter(newVector));
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.