วิธีที่ดีที่สุดในการแยก subvector จากเวกเตอร์


295

สมมติว่าผมมีstd::vector(โทรให้ของมันmyVec) Nขนาด อะไรคือวิธีที่ง่ายที่สุดในการสร้างเวกเตอร์ใหม่ซึ่งประกอบด้วยสำเนาขององค์ประกอบ X ถึง Y โดยที่ 0 <= X <= Y <= N-1 ยกตัวอย่างเช่นmyVec [100000]ผ่านในเวกเตอร์ของขนาดmyVec [100999]150000

หากไม่สามารถทำได้อย่างมีประสิทธิภาพด้วยเวกเตอร์มีประเภทข้อมูล STL อื่นที่ฉันควรใช้แทนหรือไม่


7
คุณบอกว่าคุณต้องการแยก subvector แต่สำหรับฉันแล้วสิ่งที่คุณต้องการคือมุมมอง / การเข้าถึง subvector - ความแตกต่างที่มุมมองจะไม่คัดลอก - โรงเรียนเก่า C ++ จะใช้ตัวชี้เริ่มและตัวชี้สิ้นสุด เนื่องจากข้อเท็จจริงที่ว่า mem ใน std :: vector นั้นต่อเนื่องกันดังนั้นคุณควรวนซ้ำโดยใช้พอยน์เตอร์และหลีกเลี่ยงการคัดลอกอย่างไรก็ตามถ้าคุณไม่สนใจ copy ให้เริ่มต้นเวกเตอร์ใหม่ด้วยขอบเขตของหน้าที่แล้ว เวกเตอร์
serup

มี. data () ( cplusplus.com/reference/vector/vector/data ) ตั้งแต่ c ++ 11 อย่างไรก็ตามการใช้ตัวชี้ถูกกีดกันภายในคอนเทนเนอร์ stl ดูstackoverflow.com/questions/31663770/…
David Tóth

คำตอบ:


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

เป็นการดำเนินการ O (N) เพื่อสร้างเวกเตอร์ใหม่ แต่ไม่มีวิธีที่ดีกว่าจริงๆ


12
1 ยังเป็นของ O (YX) ซึ่งน้อยกว่าหรือเท่ากับ O (N) (และในตัวอย่างของเขามากน้อย)
orip

74
@ หรือดีแล้วก็ O (N) หลังจากทั้งหมด
โยฮันน์ Gerell

55
@GregRogers: มันไม่สมเหตุสมผลที่จะใช้สัญลักษณ์ใหญ่ -O โดยที่ N เป็นตัวเลขเฉพาะ Big-O สื่อสารอัตราการเติบโตตามการเปลี่ยนแปลงของ N โยฮัน: ดีที่สุดที่จะไม่ใช้ชื่อตัวแปรหนึ่งชื่อในสองวิธี ปกติเราจะพูดอย่างใดอย่างหนึ่งหรือเราจะบอกว่าO(Y-X) O(Z) where Z=Y-X
Mooing Duck

2
@GregRogers โดยใช้วิธีนี้เราจำเป็นต้องประกาศเวกเตอร์ใหม่ มีวิธีในการเปลี่ยนเวกเตอร์ดั้งเดิมหรือไม่? อย่าง myVec (ก่อน, สุดท้าย)? ฉันรู้ว่านี่เป็นสิ่งที่ผิด แต่ฉันต้องการวิธีแก้ปัญหาจริงๆเพราะฉันต้องการใช้การเรียกซ้ำในรหัสของฉันและจำเป็นต้องใช้เวกเตอร์เดียวกันซ้ำ ๆ (แม้ว่าจะมีการเปลี่ยนแปลง) ขอบคุณ!
ulyssis2

13
ทำไมไม่เพียงvector<T> newVec(myVec.begin() + 100000, myVec.begin() + 101000);?
aquirdturtle

88

เพียงแค่ใช้ตัวสร้างเวกเตอร์

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

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

2
ตกลงฉันไม่ได้ตระหนักว่ามันเป็นเรื่องง่ายที่จะได้รับ iterator จากองค์ประกอบเวกเตอร์โดยพลการ
An Jandrew

5
การระบุที่อยู่ขององค์ประกอบเวกเตอร์เหล่านั้นคือการแฮ็กที่ไม่สามารถถอดได้ที่จะแตกหากหน่วยเก็บข้อมูลเวกเตอร์นั้นไม่ได้อยู่ติดกัน ใช้เริ่มต้น () + 100000 เป็นต้น
j_random_hacker

2
ไม่ดีของฉันเห็นได้ชัดว่ามาตรฐานรับประกันว่าที่เก็บข้อมูลเวกเตอร์นั้นต่อเนื่อง อย่างไรก็ตามมันเป็นวิธีปฏิบัติที่ไม่ถูกต้องในการทำงานกับที่อยู่เช่นนี้เนื่องจากไม่รับประกันว่าจะทำงานได้อย่างสมบูรณ์สำหรับคอนเทนเนอร์ทั้งหมดที่สนับสนุนการเข้าถึงแบบสุ่มขณะที่เริ่มต้น () + 100000 คือ
j_random_hacker

33
@j_random_hacker: ขออภัยที่ไม่เห็นด้วย ข้อมูลจำเพาะ STL สำหรับ std :: vector ได้รับการเปลี่ยนแปลงอย่างชัดเจนเพื่อรองรับขั้นตอนประเภทนี้ ตัวชี้เป็นชนิดที่ถูกต้องของตัววนซ้ำ ค้นหา iterator_traits <>
Martin York

6
@ taktak004 ไม่ จำไว้ว่าoperator[]ส่งคืนการอ้างอิง เป็นเพียงจุดที่คุณอ่านหรือเขียนข้อมูลอ้างอิงว่าจะเป็นการละเมิดการเข้าถึง เนื่องจากเราทำไม่ได้ แต่รับที่อยู่เราจึงไม่ได้เรียกใช้ UB แทน
Martin York

28

std::vector<T>(input_iterator, input_iterator)ในกรณีของคุณfoo = std::vector<T>(myVec.begin () + 100000, myVec.begin () + 150000);ดูตัวอย่างที่นี่


1
เนื่องจาก Andrew พยายามสร้างเวกเตอร์ใหม่ฉันจะแนะนำ "std :: vector foo (... " แทนที่จะคัดลอกด้วย "foo = std :: vector (... ")
Drew Dormann

4
ใช่แน่นอน แต่ไม่ว่าคุณจะพิมพ์ std :: vector <int> foo = std :: vector (... ) หรือ std :: vector <int> foo (... ) ไม่ควรสำคัญ
Anteru

19

วันนี้เราใช้spans! ดังนั้นคุณจะเขียน:

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

เพื่อรับช่วง 1,000 องค์ประกอบประเภทเดียวกันเป็นmyvecของ หรือแบบฟอร์มสั้น ๆ เพิ่มเติม:

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

(แต่ฉันไม่ชอบสิ่งนี้มากนักเนื่องจากความหมายของอาร์กิวเมนต์ที่เป็นตัวเลขแต่ละตัวนั้นไม่ชัดเจนทั้งหมดและจะแย่ลงถ้าความยาวและ start_pos มีลำดับความสำคัญเท่ากัน)

อย่างไรก็ตามโปรดจำไว้ว่านี่ไม่ใช่การคัดลอกมันเป็นเพียงมุมมองของข้อมูลในเวกเตอร์ดังนั้นควรระมัดระวัง หากคุณต้องการสำเนาจริงคุณสามารถทำได้:

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

หมายเหตุ:

  • gslหมายถึงห้องสมุดสนับสนุนแนวทาง สำหรับข้อมูลเพิ่มเติมเกี่ยวกับการgslดู: http://www.modernescpp.com/index.php/c-core-guideline-the-guidelines-support-library
  • สำหรับการนำไปปฏิบัติอย่างใดอย่างหนึ่งgslโปรดดู: https://github.com/Microsoft/GSL
  • C ++ 20 spanให้การดำเนินงานของ คุณจะใช้std::spanและมากกว่า#include <span>#include <gsl/span>
  • สำหรับข้อมูลเพิ่มเติมเกี่ยวกับช่วงโปรดดูที่: "ระยะเวลา" คืออะไรและฉันควรใช้เมื่อใด
  • std::vector มีช่างก่อสร้างเป็นล้านล้านมันง่ายมากที่จะตกอยู่ในสิ่งที่คุณไม่ต้องการใช้ดังนั้นระวัง

จะใช้cbeginและcendเพียงเพื่อหลักการ;) std::cbeginฯลฯ แม้
JHBonarius

1
@JHBonarius: ดูว่าโค้ดนี้ไม่ได้ถูกเทมเพลตในการเลือกคอนเทนเนอร์ฉันไม่เห็นว่ามีประโยชน์อะไรเป็นพิเศษ ฉันคิดว่าเป็นเรื่องของรสนิยม
einpoklum

10

หากทั้งสองจะไม่ได้รับการแก้ไข (ไม่มีการเพิ่ม / ลบรายการ - การปรับเปลี่ยนที่มีอยู่จะดีตราบเท่าที่คุณจ่ายรำลึกถึงปัญหา threading) คุณก็สามารถผ่านรอบdata.begin() + 100000และdata.begin() + 101000และแกล้งทำเป็นว่าพวกเขาเป็นbegin()และend()ของเวกเตอร์ที่มีขนาดเล็ก

หรือเนื่องจากพื้นที่จัดเก็บแบบเวกเตอร์รับประกันว่าต่อเนื่องกันคุณสามารถส่งรายการอาร์เรย์ได้ 1,000 รายการ:

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

เทคนิคทั้งสองนี้ใช้เวลาคงที่ แต่ต้องการความยาวของข้อมูลที่ไม่เพิ่มขึ้นทำให้เกิดการจัดสรรใหม่


นี่ก็เป็นสิ่งที่ดีเช่นกันถ้าคุณต้องการให้เวกเตอร์ดั้งเดิมและ subvector เชื่อมโยง
PyRulez

7

การสนทนานี้ค่อนข้างเก่า แต่สิ่งที่ง่ายที่สุดยังไม่ได้กล่าวถึงด้วยการเริ่มต้นรายการ :

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

มันต้องมี c ++ 11 ขึ้นไป

ตัวอย่างการใช้งาน:

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

ผลลัพธ์:

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

คุณไม่ได้พูดถึงประเภทstd::vector<...> myVecใด แต่ถ้าเป็นประเภทง่ายๆหรือ struct / class ที่ไม่มีตัวชี้และคุณต้องการประสิทธิภาพที่ดีที่สุดคุณสามารถทำสำเนาหน่วยความจำโดยตรง (ซึ่งฉันคิดว่าจะเร็วกว่า ให้คำตอบอื่น ๆ ) นี่เป็นตัวอย่างทั่วไปstd::vector<type> myVecซึ่งtypeในกรณีนี้คือ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
ฉันสงสัยว่าด้วย -O3, @ Anteru's "using constructor" std::vector(myVec.begin () + 100000, myVec.begin () + 150000);จะไม่สร้างเวอร์ชั่นที่ยาวกว่านี้ในการประกอบเดียวกันหรือไม่
sandthorn

1
ตัวอย่างเช่น MSVC ++ 2015 รวมstd::vector<>(iter, iter)ถึงmemmove()ถ้าเหมาะสม (หากตัวสร้างเป็นเรื่องเล็กน้อยสำหรับคำจำกัดความที่เหมาะสมของเรื่องเล็กน้อย)
Pablo H

1
memcpyอย่าเรียก ทำstd::copyหรือคอนสตรัคเตอร์ซึ่งยอมรับช่วง (สองตัววนซ้ำ) และคอมไพเลอร์และ std.library จะสมคบกันที่จะเรียกmemcpyตามความเหมาะสม
Bulletmagnet


3

คุณสามารถใช้สำเนา STLพร้อมประสิทธิภาพ O (M) เมื่อ M คือขนาดของ subvector


เพิ่มขึ้นเพราะมันชี้ให้ฉันไปในทิศทางที่ถูกต้อง แต่ฉันสามารถเห็นได้ว่าทำไม @LokiAstari แนะนำว่ามันไม่ใช่ตัวเลือกที่ถูกต้อง - เนื่องจาก STL :: copy ใช้งานได้กับ std :: vector <T> สองอาร์เรย์ที่มีขนาดและประเภทเดียวกัน ที่นี่ OP ต้องการคัดลอกส่วนย่อยลงในอาร์เรย์ใหม่ที่มีขนาดเล็กลงตามที่ระบุไว้ในโพสต์ของ OP: "0 <= X <= Y <= N-1"
Andrew

@Andrew ดูตัวอย่างโดยใช้ std :: copy และ std :: back_inserter
chrisg

@ LokiAstari ทำไมล่ะ
chrisg

2
@LokiAstari ฉันหมายถึงการแก้ไขสิ่งนี้ซึ่งไม่รอดจากการทบทวนโดยเพื่อนซึ่งวางตัวอย่าง <br/> เวกเตอร์ <T> newvec; std :: copy (myvec.begin () + 10,000, myvec.begin () +10100, std :: back_inserter (newvec)); <br/> ในกรณีนี้คุณไม่จำเป็นต้องสร้างปลายทางก่อน แต่แน่นอนว่าการกำหนดค่าเริ่มต้นโดยตรงนั้นโดยตรง ... มากกว่า
chrisg

1
@chrisg: มันสองบรรทัด นอกจากนี้คุณต้องติดบรรทัดที่สามเพื่อให้แน่ใจว่ามีประสิทธิภาพ newvec.reserve(10100 - 10000);. ตัวเลือกมันแน่นอนและในทางเทคนิคมันจะทำงาน แต่จากสองข้อที่คุณจะแนะนำ?
Martin York

1

วิธีเดียวที่จะฉายคอลเลกชันที่ไม่ใช่เวลาเชิงเส้นคือการทำอย่างเกียจคร้านซึ่ง "เวกเตอร์" ที่เกิดขึ้นจริง ๆ แล้วเป็นประเภทย่อยที่มอบหมายให้คอลเลกชันดั้งเดิม ตัวอย่างเช่นList#subseqวิธีของ Scala สร้างลำดับย่อยในเวลาคงที่ อย่างไรก็ตามวิธีนี้ใช้ได้เฉพาะในกรณีที่คอลเล็กชันไม่มีการเปลี่ยนแปลงและถ้าคอลเลกชันขยะเป็นภาษากีฬา


ในวิธีการทำ c ++ ที่จะมี vector ของ shared_ptr เป็น X แทนที่จะเป็น vector ของ X แล้วคัดลอก SPs แต่น่าเสียดายที่ฉันไม่คิดว่ามันจะเร็วกว่าเพราะการทำงานของอะตอมเกี่ยวข้องกับ cpying SP หรือเวกเตอร์ดั้งเดิมอาจเป็น const shared_ptr ของเวกเตอร์แทนและคุณก็ใช้อ้างอิงถึงช่วงย่อยนั้น ofc คุณไม่จำเป็นต้องทำให้มันเป็น shared_ptr ของเวกเตอร์ แต่แล้วคุณมีปัญหาตลอดชีวิต ... ทั้งหมดนี้อยู่ด้านบนของหัวของฉันอาจจะผิด ...
NoSenseEtAl

0

โพสต์นี้ล่าช้าสำหรับคนอื่น ๆ .. ฉันเดิมพัน coder แรกเสร็จแล้ว สำหรับประเภทข้อมูลที่เรียบง่ายไม่จำเป็นต้องมีการคัดลอกเพียงแค่เปลี่ยนกลับไปใช้วิธีการรหัส C แบบเก่าที่ดี

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

จากนั้นส่งผ่านตัวชี้ p และ len ไปยังสิ่งที่ต้องการ subvector

notelen ต้องเป็น !! len < myVec.size()-start


นี่ไม่ได้ทำการคัดลอก
Trilarion

0

อาจจะเป็นarray_view / spanในไลบรารี GSL อาจเป็นตัวเลือกที่ดี

ที่นี่ยังเป็นการนำไฟล์เดียว: array_view


กรุณาเพิ่มคำตอบที่นี่พร้อมกับลิงค์ ลิงก์ภายนอกอาจเปลี่ยนแปลงในอนาคต
Panther

0

คัดลอกองค์ประกอบจากเวกเตอร์หนึ่งไปอีกอันได้อย่างง่ายดาย
ในตัวอย่างนี้ฉันใช้เวกเตอร์ของคู่เพื่อให้ง่ายต่อการเข้าใจ
`

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

'
อย่างที่คุณเห็นคุณสามารถคัดลอกองค์ประกอบจากเวกเตอร์หนึ่งไปยังอีกเวกเตอร์หนึ่งได้อย่างง่ายดายหากคุณต้องการคัดลอกองค์ประกอบจากดัชนี 10 ถึง 16 เช่นนั้นเราจะใช้

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

และถ้าคุณต้องการองค์ประกอบจากดัชนี 10 ถึงบางดัชนีจากจุดสิ้นสุดในกรณีนั้น

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

หวังว่าสิ่งนี้จะช่วยได้โปรดจำไว้ในกรณีสุดท้าย v.end()-5 > v.begin()+10


0

อีกตัวเลือกหนึ่ง: มีประโยชน์เช่นเมื่อย้ายระหว่าง a thrust::device_vectorและ a thrust::host_vectorซึ่งคุณไม่สามารถใช้ตัวสร้าง

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

ควรมีความซับซ้อน O (N)

คุณสามารถรวมสิ่งนี้กับรหัสแอนเวอร์ด้านบน

vector<T>::const_iterator first = myVec.begin() + 100000;
vector<T>::const_iterator last = myVec.begin() + 101000;
std::copy(first, last, std::back_inserter(newVector));
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.