Cách tốt nhất để trích xuất một subvector từ một vector?


295

Giả sử tôi có một std::vector(hãy gọi nó myVec) kích thước N. Cách đơn giản nhất để xây dựng một vectơ mới bao gồm một bản sao của các phần tử X đến Y, trong đó 0 <= X <= Y <= N-1? Ví dụ, myVec [100000]thông qua myVec [100999]một vectơ kích thước 150000.

Nếu điều này không thể được thực hiện hiệu quả với một vectơ, thì có một kiểu dữ liệu STL khác mà tôi nên sử dụng thay thế không?


7
bạn nói rằng bạn muốn trích xuất một trình con, nhưng đối với tôi, điều bạn thực sự muốn là một khung nhìn / quyền truy cập vào trình con - sự khác biệt là một khung nhìn sẽ không sao chép - trường C ++ cũ sẽ sử dụng con trỏ bắt đầu và con trỏ kết thúc, với thực tế là mem trên std :: vector không liên tục, thì bạn có thể lặp lại bằng cách sử dụng các con trỏ và do đó tránh sao chép, tuy nhiên nếu bạn không sao chép, thì chỉ cần khởi tạo một vectơ mới với phạm vi trước đó của bạn vector
serup

Có .data () ( cplusplus.com/reference/vector/vector/data ) kể từ c ++ 11. Tuy nhiên, việc sử dụng các con trỏ không được khuyến khích trong các thùng chứa stl, hãy xem stackoverflow.com/questions/31663770/ mẹo
David Tóth

Câu trả lời:


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

Đây là một hoạt động O (N) để xây dựng vectơ mới, nhưng thực sự không có cách nào tốt hơn.


12
+1, cũng là O (YX), nhỏ hơn hoặc bằng O (N) (và trong ví dụ của anh ta ít hơn nhiều)
orip

74
@orip Vâng, sau đó nó là O (N).
Johann Gerell

55
@GregRogers: Không có nghĩa gì khi sử dụng ký hiệu big-O trong đó N là một số cụ thể. Big-O truyền đạt tốc độ tăng trưởng liên quan đến cách N thay đổi. Johann: Tốt nhất không nên sử dụng một tên biến theo hai cách. Chúng tôi thường nói O(Y-X), hoặc chúng tôi sẽ nói O(Z) where Z=Y-X.
Vịt Mooing

2
@GregRogers Bằng cách sử dụng cách này, chúng ta cần khai báo một vectơ mới. Có cách nào để thay đổi vector ban đầu không? một cái gì đó như myVec (đầu tiên, cuối cùng)? Tôi biết điều này là sai, nhưng tôi thực sự cần giải pháp vì tôi muốn sử dụng đệ quy trong mã của mình và cần phải sử dụng lại cùng một vectơ (mặc dù đã thay đổi). Cảm ơn!
ulyssis2

13
Tại sao không chỉ vector<T> newVec(myVec.begin() + 100000, myVec.begin() + 101000);?
aquirdturtle

88

Chỉ cần sử dụng các hàm tạo vector.

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

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

2
Ok, tôi đã không nhận ra rằng thật đơn giản để có được một trình vòng lặp từ một phần tử vectơ tùy ý.
Trả lời

5
Lấy địa chỉ của các phần tử vectơ đó là một hack không thể tấn công sẽ bị phá vỡ nếu bộ lưu trữ vector không thực sự liền kề. Sử dụng bắt đầu () + 100000, v.v.
j_random_hacker

2
Xấu của tôi, rõ ràng là tiêu chuẩn đảm bảo rằng lưu trữ vector là liên tục. Tuy nhiên, thực tế không tốt khi làm việc với các địa chỉ như thế này vì nó chắc chắn không được đảm bảo để hoạt động cho tất cả các container hỗ trợ truy cập ngẫu nhiên, trong khi bắt đầu () + 100000 là.
j_random_hacker

33
@j_random_hacker: Xin lỗi phải không đồng ý. Đặc tả STL cho std :: vector đã được thay đổi rõ ràng để hỗ trợ loại thủ tục này. Ngoài ra một con trỏ là loại iterator hợp lệ. Tra cứu iterator_traits <>
Martin York

6
@ taktak004 Không. Hãy nhớ rằng operator[]trả về một tham chiếu. Nó chỉ ở điểm mà bạn đọc hoặc viết tài liệu tham khảo rằng nó sẽ trở thành một vi phạm truy cập. Vì chúng tôi không làm nhưng thay vào đó nhận được địa chỉ, chúng tôi chưa gọi UB ,.
Martin York

28

std::vector<T>(input_iterator, input_iterator), trong trường hợp của bạn foo = std::vector<T>(myVec.begin () + 100000, myVec.begin () + 150000);, xem ví dụ ở đây


1
Vì Andrew đang cố gắng xây dựng một vectơ mới, tôi sẽ đề xuất "std :: vector foo (..." thay vì sao chép bằng "foo = std :: vector (..."
Drew Dormann

4
Vâng, tất nhiên, nhưng dù bạn nhập std :: vector <int> foo = std :: vector (...) hay std :: vector <int> foo (...) thì không thành vấn đề.
Anteru

19

Những ngày này, chúng tôi sử dụng spans! Vì vậy, bạn sẽ viết:

#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);

để có được khoảng 1000 phần tử cùng loại với myvec's. Hoặc một hình thức ngắn gọn hơn:

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

(nhưng tôi không thích điều này nhiều vì ý nghĩa của từng đối số số không hoàn toàn rõ ràng; và sẽ tệ hơn nếu độ dài và start_pose có cùng độ lớn.)

Dù sao, hãy nhớ rằng đây không phải là bản sao, nó chỉ là chế độ xem dữ liệu trong vectơ, vì vậy hãy cẩn thận. Nếu bạn muốn có một bản sao thực sự, bạn có thể làm:

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

Ghi chú:


sẽ sử dụng cbegincendchỉ cho các nguyên tắc;) std::cbeginvv thậm chí.
JHBonarius

1
@JHBonarius: Xem cách mã này không được đặt theo lựa chọn của container, tôi không thấy có một lợi ích cụ thể nào; một vấn đề của hương vị tôi cho rằng.
einpoklum

10

Nếu cả hai sẽ không được sửa đổi (không thêm / xóa các mục - sửa đổi những cái hiện có là tốt miễn là bạn trả tiền chú ý đến các vấn đề luồng), bạn chỉ có thể vượt qua xung quanh data.begin() + 100000data.begin() + 101000, và giả vờ rằng họ là nhữngbegin()end()của một vector nhỏ hơn.

Hoặc, vì lưu trữ vector được đảm bảo liền kề nhau, bạn chỉ cần chuyển qua một mảng 1000 mục:

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

Cả hai kỹ thuật này đều mất thời gian liên tục, nhưng yêu cầu độ dài của dữ liệu không tăng lên, gây ra sự phân bổ lại.


Điều này cũng tốt nếu bạn muốn vector ban đầu và subvector được liên kết.
PyRulez

7

Cuộc thảo luận này khá cũ, nhưng cuộc thảo luận đơn giản nhất chưa được đề cập, với việc khởi tạo danh sách :

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

Nó đòi hỏi c ++ 11 trở lên.

Ví dụ sử dụng:

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

Kết quả:

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

Bạn đã không đề cập đến loại nào std::vector<...> myVec, nhưng nếu đó là loại đơn giản hoặc cấu trúc / lớp không bao gồm con trỏ và bạn muốn có hiệu quả tốt nhất, thì bạn có thể thực hiện sao chép bộ nhớ trực tiếp (mà tôi nghĩ sẽ nhanh hơn câu trả lời khác được cung cấp). Dưới đây là một ví dụ chung của std::vector<type> myVecnơi typetrong trường hợp này là 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
Tôi tự hỏi nếu với "sử dụng hàm tạo" của -O3, @ Anteru std::vector(myVec.begin () + 100000, myVec.begin () + 150000);, liệu phiên bản dài hơn của sản phẩm này có thành cùng một tổ hợp không?
Sandthorn

1
MSVC ++ 2015, ví dụ, biên dịch std::vector<>(iter, iter)thành memmove(), nếu phù hợp (nếu hàm tạo là tầm thường, cho một định nghĩa phù hợp về tầm thường).
Pablo H

1
Đừng gọi memcpy. Thực hiện một std::copyhoặc một hàm tạo chấp nhận một phạm vi (hai trình lặp) và trình biên dịch và std.l Library sẽ âm mưu gọi memcpykhi thích hợp.
Bulletmagnet

4

Bạn chỉ có thể sử dụng insert

vector<type> myVec { n_elements };

vector<type> newVec;

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

3

Bạn có thể sử dụng bản sao STL với hiệu suất O (M) khi M là kích thước của bộ con.


Được nâng cấp bởi vì nó chỉ cho tôi đi đúng hướng nhưng tôi có thể hiểu tại sao @LokiAstari đề nghị đó không phải là lựa chọn chính xác - vì bản sao STL :: hoạt động với hai mảng std :: vector <T> có cùng kích thước và loại. Ở đây, OP muốn sao chép một phần phụ vào một mảng mới, nhỏ hơn như được nêu ở đây trong bài đăng của OP: "0 <= X <= Y <= N-1"
Andrew

@Andrew, xem ví dụ sử dụng std :: copy và std :: back_inserter
chrisg 26/07/17

@LokiAstari tại sao không?
chrisg

2
@LokiAstari Tôi đã đề cập đến một chỉnh sửa cho điều này mà không tồn tại đánh giá ngang hàng, trong đó đưa ra ví dụ <br/> vector <T> newvec; std :: copy (myvec.begin () + 10000, myvec.begin () +10100, std :: back_inserter (newvec)); Trong trường hợp này, bạn không cần phải xây dựng đích trước, nhưng chắc chắn, việc khởi tạo trực tiếp sẽ ... trực tiếp hơn.
chrisg

1
@chrisg: Nó cũng có hai dòng. Ngoài ra, bạn cần phải dán một dòng thứ ba để đảm bảo nó hiệu quả. newvec.reserve(10100 - 10000);. CNTT chắc chắn là một lựa chọn và về mặt kỹ thuật nó sẽ hoạt động. Nhưng trong số hai bạn sẽ giới thiệu?
Martin York

1

Cách duy nhất để chiếu một bộ sưu tập không phải là thời gian tuyến tính là làm một cách lười biếng, trong đó "vectơ" kết quả thực sự là một kiểu con mà ủy nhiệm cho bộ sưu tập ban đầu. Ví dụ, List#subseqphương pháp của Scala tạo ra một chuỗi con trong thời gian không đổi. Tuy nhiên, điều này chỉ hoạt động nếu bộ sưu tập là bất biến và nếu ngôn ngữ cơ bản là bộ sưu tập rác thể thao.


theo cách của c ++ để làm điều đó sẽ có vectơ shared_ptr thành X thay vì vectơ của X và sau đó sao chép SP, nhưng thật không may, tôi không nghĩ rằng điều đó nhanh hơn vì hoạt động nguyên tử liên quan đến SP cpying. Hoặc vectơ ban đầu có thể là một const shared_ptr của vectơ thay vào đó và bạn chỉ cần tham khảo để sắp xếp lại trong đó. Tất nhiên, bạn không cần phải biến nó thành một vectơ chia sẻ nhưng sau đó bạn gặp vấn đề suốt đời ... tất cả điều này nằm ngoài đầu tôi, có thể sai ...
NoSenseEtAl

0

Gửi bài này muộn chỉ cho người khác..Tôi cá là lập trình viên đầu tiên được thực hiện ngay bây giờ. Đối với các kiểu dữ liệu đơn giản, không cần sao chép, chỉ cần hoàn nguyên các phương thức mã C cũ.

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

Sau đó chuyển con trỏ p và len tới bất cứ thứ gì cần một bộ con.

phải là notelen !! len < myVec.size()-start


Điều này không thực hiện một bản sao.
Trilarion

0

Có lẽ mảng_view / span trong thư viện GSL là một lựa chọn tốt.

Đây cũng là một triển khai tập tin duy nhất: Array_view .


Vui lòng thêm câu trả lời ở đây cùng với liên kết. Vì liên kết bên ngoài có thể thay đổi trong tương lai
Panther

0

Sao chép các phần tử từ vectơ này sang vectơ khác một cách dễ dàng
Trong ví dụ này, tôi đang sử dụng một vectơ cặp để dễ hiểu
`

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)]

'
Như bạn thấy, bạn có thể dễ dàng sao chép các phần tử từ vectơ này sang vectơ khác, nếu bạn muốn sao chép các phần tử từ chỉ mục 10 đến 16 chẳng hạn thì chúng tôi sẽ sử dụng

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

và nếu bạn muốn các phần tử từ chỉ mục 10 đến một số chỉ mục từ cuối, thì trong trường hợp đó

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

Hy vọng điều này sẽ giúp, chỉ cần nhớ trong trường hợp cuối cùng v.end()-5 > v.begin()+10


0

Một tùy chọn khác: Chẳng hạn hữu ích khi di chuyển giữa a thrust::device_vectorvà a thrust::host_vector, trong đó bạn không thể sử dụng hàm tạo.

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

Cũng nên phức tạp O (N)

Bạn có thể kết hợp điều này với mã anwer hàng đầu

vector<T>::const_iterator first = myVec.begin() + 100000;
vector<T>::const_iterator last = myVec.begin() + 101000;
std::copy(first, last, std::back_inserter(newVector));
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.