C ++, คัดลอกชุดเป็นเวกเตอร์


146

ฉันต้องการคัดลอกstd::setไปที่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

ปัญหาอยู่ที่ไหน


5
นอกจากนี้ยังมีassign()ฟังก์ชั่น:output.assign(input.begin(), input.end());
Gene Bushuyev

เวกเตอร์ของคุณว่างเปล่า มีหลากหลายวิธีในการแก้ไขที่เป็นเหมือนคนกำลังชี้ด้านล่าง
AJG85

@Gene: มอบหมาย () ต้องการจอง () จำนวนที่จำเป็นในการเก็บข้อมูลล่วงหน้า มันจะใช้อินพุตตัววนซ้ำเพื่อกำหนดจำนวนที่ต้องการยกเว้นว่าตัววนซ้ำนั้นเป็นตัวป้อนข้อมูลแบบเข้มงวดซึ่งในกรณีนี้จะข้ามการจองและส่งผลให้มีการจัดสรรใหม่ในทุก ๆ push_back () ในฝั่งตรงข้ามของสเปกตรัม BiderectionalIterators จะยอมให้ลบเพียงจุดเริ่มต้น std :: set iterators แต่ไม่ใช่ (เป็น ForwardIterator) และนั่นก็โชคร้าย: ในกรณีนี้ assign () จะเดินทั้งชุดเพื่อกำหนดขนาด - ประสิทธิภาพที่ไม่ดีในชุดใหญ่
Sergey Shevchenko

คำตอบ:


213

คุณต้องใช้back_inserter:

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

std::copyไม่เพิ่มองค์ประกอบลงในคอนเทนเนอร์ที่คุณใส่: ไม่สามารถ; มีเพียงตัววนซ้ำในคอนเทนเนอร์เท่านั้น ด้วยเหตุนี้ถ้าคุณผ่านตัววนซ้ำผลลัพธ์โดยตรงไปยังstd::copyคุณต้องทำให้แน่ใจว่ามันชี้ไปที่ช่วงที่มีขนาดใหญ่พอที่จะเก็บช่วงอินพุตได้

std::back_inserterสร้างตัววนซ้ำเอาต์พุตที่เรียกpush_backใช้คอนเทนเนอร์สำหรับแต่ละองค์ประกอบดังนั้นแต่ละองค์ประกอบจะถูกแทรกลงในคอนเทนเนอร์ หรือคุณอาจสร้างองค์ประกอบจำนวนเพียงพอในstd::vectorเพื่อเก็บช่วงที่ถูกคัดลอก:

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

หรือคุณสามารถใช้ตัวstd::vectorสร้างช่วง:

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

3
สวัสดี James แทนที่จะเป็นบรรทัด :: สำเนาของคุณ (บล็อคโค้ดแรกในคำตอบของคุณ) ฉันไม่สามารถทำoutput.insert(output.end(), input.begin(), input.end());แทนได้หรือไม่
user2015453

หรือแค่ใช้รุ่น cbegin และ cend: output.insert(output.cend(), input.cbegin(), input.cend());คุณคิดอย่างไร? ขอบคุณ
user2015453

2
ฉันควร output.reserve (input.size ()); ด้วยตัวเองหรือฉันหวังว่าผู้แปลบางคนจะทำเพื่อฉัน
jimifiki

@ jimifiki ไม่หวังว่าฉันกลัว
Alexis Wilke

การเริ่มต้นเวกเตอร์ครั้งแรกของคุณไม่ถูกต้อง คุณสร้างอาร์เรย์ของinput,size()รายการที่ว่างเปล่าแล้วผนวกผนวกหลังจากนั้น std::vector<double> output; output.reserve(input.size()); std::copy(...);ฉันคิดว่าคุณหมายถึงการใช้งาน
Alexis Wilke

121

เพียงใช้ตัวสร้างสำหรับเวกเตอร์ที่ใช้ตัววนซ้ำ:

std::set<T> s;

//...

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

สมมติว่าคุณต้องการเนื้อหาของ s ใน v และไม่มีอะไรใน v ก่อนที่จะคัดลอกข้อมูลไปยังมัน



24

คุณไม่ได้จองพื้นที่เพียงพอในวัตถุเวกเตอร์ของคุณเพื่อเก็บเนื้อหาของชุดของคุณ

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

1
สิ่งนี้ไม่สมควร -1 โดยเฉพาะอย่างยิ่งสิ่งนี้อนุญาตให้เวกเตอร์ทำการจัดสรรเพียงครั้งเดียว (เนื่องจากไม่สามารถกำหนดระยะทางของตัววนซ้ำที่ตั้งค่าใน O (1)) และถ้ามันไม่ได้กำหนดไว้สำหรับเวกเตอร์ที่จะให้ศูนย์แต่ละองค์ประกอบเมื่อสร้าง คุ้มค่าที่จะอนุญาตให้สำเนาต้มลงไปใน memcpy ส่วนหลังยังคงคุ้มค่าหากการนำตัวเลขลูปใน ctor ของเวกเตอร์ไปใช้สามารถลบออกได้ แน่นอนว่าอดีตสามารถสำเร็จได้ด้วยการสำรอง
Fred Nurk

ฉันไม่รู้ ให้ฉันช่วยคุณด้วย
wilhelmtell

ฉันให้คุณ -1 แต่มันเป็น thinko ในส่วนของฉัน ทำการแก้ไขเล็กน้อยเพื่อให้ฉันสามารถยกเลิกการลงคะแนนของฉันและฉันจะให้ +1: นี่เป็นวิธีแก้ปัญหาที่สะอาดมากเพราะสถานที่ให้บริการล้มเหลวเป็นครั้งแรก
Fred Foo

ฉันแค่คิดออกเท่านั้นว่าถ้าฉันแก้ไขคำตอบด้วยตัวเองฉันสามารถลงคะแนนได้ นั่นทำให้คุณได้รับ +1 สำหรับการจัดสรรหน่วยความจำที่ล้มเหลวครั้งแรก ขออภัย!
Fred Foo

3

ฉันคิดว่าวิธีที่มีประสิทธิภาพที่สุดคือการจัดสรรล่วงหน้าและจัดวางองค์ประกอบต่างๆ:

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

ด้วยวิธีนี้เราจะเรียกใช้ตัวคัดลอกคอนสตรัคเตอร์สำหรับทุกองค์ประกอบเท่านั้นเมื่อเทียบกับการเรียกคอนสตรัคเตอร์เริ่มต้นก่อนจากนั้นจึงคัดลอกโอเปอเรเตอร์การมอบหมายสำหรับโซลูชันอื่น ๆ ชี้แจงเพิ่มเติมด้านล่าง

  1. อาจใช้back_inserterแต่จะเรียกใช้ push_back () บนเวกเตอร์ ( https://en.cppreference.com/w/cpp/iterator/back_insert_iterator ) emplace_back ()มีประสิทธิภาพมากขึ้นเพราะมันหลีกเลี่ยงการสร้างชั่วคราวเมื่อใช้push_back () มันไม่ได้เป็นปัญหากับประเภทที่สร้างขึ้นเล็กน้อย แต่จะเป็นความหมายของประสิทธิภาพสำหรับประเภทที่ไม่ได้สร้างขึ้นมาเล็กน้อย (เช่นสตริง std :: string)

  2. เราจำเป็นต้องหลีกเลี่ยงการสร้างเวกเตอร์ด้วยอาร์กิวเมนต์ขนาดซึ่งทำให้องค์ประกอบทั้งหมดเริ่มต้นที่สร้างขึ้น (เพื่ออะไร) เช่นเดียวกับโซลูชันที่ใช้std :: copy ()เช่น

  3. และท้ายที่สุดvector :: assign () method หรือ constructor ที่ใช้ช่วงตัววนซ้ำนั้นไม่ใช่ตัวเลือกที่ดีเพราะพวกมันจะเรียกใช้ std :: distance () (เพื่อทราบจำนวนองค์ประกอบ) ในชุดตัววนซ้ำ สิ่งนี้จะทำให้เกิดการวนซ้ำเพิ่มเติมที่ไม่พึงประสงค์ผ่านองค์ประกอบชุดทั้งหมดเนื่องจากชุดนั้นเป็นโครงสร้างข้อมูล Binary Search Tree และไม่ได้ใช้ตัววนซ้ำการเข้าถึงแบบสุ่ม

หวังว่าจะช่วย


โปรดเพิ่มการอ้างอิงถึงผู้มีอำนาจว่าทำไมจึงรวดเร็วและเป็นเช่นนี้ทำไมback_inserterไม่จำเป็นต้องใช้
Tarick Welling

เพิ่มคำอธิบายเพิ่มเติมในคำตอบ
dshvets1

1

std::copyไม่สามารถใช้เพื่อแทรกลงในคอนเทนเนอร์เปล่า ในการทำเช่นนั้นคุณต้องใช้ insert_iterator ดังนี้:

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

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

3
สิ่งนี้จะล้มเหลวในครั้งแรกที่เวกเตอร์จัดสรรใหม่: ตัววนซ้ำจาก output.begin () ได้รับการตรวจสอบความถูกต้อง
Fred Nurk
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.