วิธีที่ดีที่สุดในการเชื่อมสองเวกเตอร์คืออะไร?


189

ฉันกำลังใช้มัลติเธรดและต้องการผสานผลลัพธ์ ตัวอย่างเช่น:

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

ฉันต้องการให้ AB ต้องมีเนื้อหาของ A และเนื้อหาของ B ตามลำดับนั้น วิธีที่มีประสิทธิภาพมากที่สุดในการทำอะไรเช่นนี้คืออะไร?


1
หากมองหาประสิทธิภาพเมื่อคุณทำงานกับคอนเทนเนอร์ขนาดใหญ่อาจมีประสิทธิภาพมากกว่าในการใช้ลิสต์ซึ่งคุณสามารถประกบกันระหว่างกันด้วยการใช้ตัวชี้ต่าง ๆ แต่รายการมีค่าใช้จ่ายพื้นที่ (พิจารณาใช้รายการเชื่อมโยงเดียว)
Kemin Zhou

คำตอบ:


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
ขอบคุณ! คงไม่คิดที่จะจอง
jmasterx

10
ควรคัดลอกแต่ละองค์ประกอบดังนั้นมันจึงเป็น O (n)
Kirill V. Lyadvinsky

1
ไม่แน่ใจว่าจะถามคำถามใหม่หรือไม่ แต่คำตอบนี้สามารถปรับปรุงได้หรือไม่เมื่อต้องย้ายความหมายเข้าบัญชี? มีวิธีที่ฉันสามารถคาดหวัง / แนะนำคอมไพเลอร์ที่จะย้ายหน่วยความจำเดียวแทนที่จะวนลูปมากกว่าองค์ประกอบทั้งหมดหรือไม่
Broes De Cat

2
@ เลขที่บอยมันเป็นเวลาคงที่ตัดจำหน่ายเพื่อ push_back องค์ประกอบหนึ่ง หากต้องการผลักองค์ประกอบกลับคืนคือ O (n)
Konrad Lindenbach

1
@ Konrad ฉันไม่ได้หมายความอย่างอื่น แต่ขอขอบคุณสำหรับการชี้แจง หมายเหตุความซับซ้อนของการดำเนินการแทรกไม่เคยได้รับในแง่ของจำนวนขององค์ประกอบที่ถูกแทรก - ซึ่งจะให้ O (n) เสมอ - แต่ในแง่ของจำนวนองค์ประกอบที่มีอยู่แล้วในภาชนะตั้งแต่นั้นให้วัดการขยายขีดความสามารถของมัน .
boycy

65

นี่คือสิ่งที่ฟังก์ชั่นสมาชิกstd::vector::insertมีไว้เพื่อ

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

4
@Nick: ช้าเมื่อเทียบกับอะไร
GManNickG

2
บางทีมันอาจตรวจสอบพื้นที่ว่างเพียงพอในแต่ละส่วนแทรก? การใช้การสำรองล่วงหน้าจะทำให้เร็วขึ้น
RvdK

10
@Nick: ฉันจะไม่แปลกใจถ้าการใช้ stdlib ที่ทันสมัยทุกอย่างมีความเชี่ยวชาญinsertในการเข้าถึงแบบวนซ้ำและสงวนไว้ล่วงหน้า
GManNickG

1
@Gman: นั่นเป็นประเด็นที่ยุติธรรมเพราะเรารู้ว่าแหล่งที่มานั้นเป็นเวกเตอร์ (ที่ตัววนซ้ำdistanceมีความซับซ้อน O (1)) ถึงกระนั้นการรับประกันประสิทธิภาพinsertก็เป็นสิ่งที่ควรคำนึงถึงเมื่อคุณสามารถทำได้ดีกว่าโดยการวางแผนล่วงหน้า
Nick Bastin

2
@RvdK การตรวจสอบพื้นที่เป็นเพียงคำแนะนำ: ความจุโหลดเปรียบเทียบกับขนาดการกระโดดตามเงื่อนไข ทั้งหมดนี้เป็นค่าใช้จ่ายเล็กน้อยสำหรับกรณีส่วนใหญ่ เนื่องจากsize < capacityเวลาส่วนใหญ่การคาดคะเนสาขาจะทำให้คำสั่งของสาขาที่ไม่ได้ทำการจัดสรรใหม่อยู่ในขั้นตอนการสอนลดความล่าช้าของสาขาที่เกิดขึ้นยกเว้นการนับซ้ำต่ำ นี่ถือว่าเป็นการใช้เวกเตอร์ที่ดีรวมถึงขั้นตอนการสอนของ CPU & การคาดคะเนสาขา [ดี] แต่สิ่งเหล่านี้เป็นข้อสันนิษฐานที่น่าเชื่อถือสำหรับเครื่องมือที่ทันสมัยและเครื่องเดสก์ท็อป ไม่ทราบเกี่ยวกับสมาร์ทโฟน ..
boycy

27

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

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

จะให้สิ่งนี้กับคุณ

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

ควรให้คุณ

1
2
3
4
5
6

Note boost :: join ไม่ได้คัดลอกสองเวกเตอร์ลงในคอนเทนเนอร์ใหม่ แต่สร้างคู่ของตัววนซ้ำ (ช่วง) ที่ครอบคลุมช่วงของคอนเทนเนอร์ทั้งสอง จะมีบางส่วนของค่าใช้จ่ายประสิทธิภาพ แต่อาจน้อยกว่าที่การคัดลอกข้อมูลทั้งหมดไปยังคอนเทนเนอร์ใหม่ก่อน


1
ความคิดดี. หลังจากคิดไปสักพักฉันก็ตระหนักว่าเป้าหมายนี้อาจจะสำเร็จได้โดยไม่ต้องใช้ห้องสมุดเพิ่ม ฉันโพสต์คำตอบเพื่ออธิบายว่า
Ronald Souza

11

จากคำตอบของ Kiril V. Lyadvinskyฉันได้สร้างเวอร์ชั่นใหม่ ตัวอย่างนี้ใช้เทมเพลตและการโหลดมากเกินไป ด้วยคุณสามารถเขียนและvector3 = vector1 + vector2 vector4 += vector3หวังว่ามันจะช่วยได้

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
คุณหมายถึงการเพิ่มองค์ประกอบของแต่ละเวกเตอร์เข้าด้วยกันหรือไม่? หรือคุณตั้งใจจะต่อท้าย? ชัดเจนในขณะนี้ แต่สำหรับ 5 ปีถัดไป .. คุณไม่ควรโอเวอร์โหลดโอเปอเรเตอร์หากความหมายคลุมเครือ
SR

2
@SR ฉันหมายถึงการเรียงลำดับ ฉันเขียนคำตอบนี้เมื่อ 3 ปีก่อน ฉันยังรู้ว่ามันหมายถึงอะไร ไม่มีปัญหา ถ้า C ++ สามารถให้โอเวอร์โหลดของตัวเองได้จะดียิ่งขึ้น (และใช่::จะถูกนำไป;)
aloisdg ย้ายไปยัง codidact.com

แน่นอนไม่ชัดเจนโดยทั่วไปที่v1 + v2ไม่ได้เป็นตัวแทนเพิ่มเติม
Apollys รองรับ Monica


ทางเลือกอื่นคือใช้@เหมือนใน F #
aloisdg กำลังจะย้ายไปที่ codidact.com

6

ในทิศทางของคำตอบ Bradgonesurfing ของหลายต่อหลายครั้งหนึ่งไม่ได้จริงๆต้องไปเชื่อมสองเวกเตอร์ (O (n)) แต่แทนที่จะเป็นเพียงแค่การทำงานกับพวกเขาราวกับว่าพวกเขาตัดแบ่ง (O (1)) หากเป็นกรณีของคุณก็สามารถทำได้โดยไม่ต้องใช้ห้องสมุด Boost

เคล็ดลับคือการสร้างพร็อกซีเวกเตอร์: คลาส wrapper ที่จัดการการอ้างอิงไปยังเวกเตอร์ทั้งสองซึ่งมองจากภายนอกเป็นหนึ่งเดียวที่ต่อเนื่องกัน

การใช้งาน

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

การดำเนินงาน

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

ผลประโยชน์หลัก ๆ

มันเป็น O (1) (เวลาคงที่) เพื่อสร้างและมีการจัดสรรหน่วยความจำเพิ่มเติมน้อยที่สุด

บางสิ่งที่ควรพิจารณา

  • คุณควรไปได้ถ้าคุณรู้ว่าจริงๆสิ่งที่คุณทำเมื่อต้องรับมือกับการอ้างอิง การแก้ปัญหานี้มีจุดมุ่งหมายเพื่อวัตถุประสงค์เฉพาะของคำถามที่ทำเพื่อที่จะทำงานสวยดี ในการใช้งานในบริบทอื่น ๆ อาจนำไปสู่พฤติกรรมที่ไม่คาดคิดหากคุณไม่แน่ใจว่าการอ้างอิงทำงานอย่างไร
  • ในตัวอย่างนี้ AB ไม่ได้จัดเตรียมโอเปอเรเตอร์ที่ไม่ใช่แบบ const ([]) อย่าลังเลที่จะรวมมัน แต่โปรดจำไว้ว่า: เนื่องจาก AB มีการอ้างอิงการกำหนดค่าจะส่งผลกระทบต่อองค์ประกอบดั้งเดิมภายใน A และ / หรือ B ไม่ว่าจะเป็นคุณลักษณะที่ต้องการหรือไม่มันเป็นคำถามเฉพาะแอปพลิเคชันที่ควร พิจารณาอย่างรอบคอบ
  • การเปลี่ยนแปลงใด ๆ ที่ทำกับ A หรือ B โดยตรง (เช่นการกำหนดค่าการเรียงลำดับ ฯลฯ ) จะ "แก้ไข" AB ด้วย สิ่งนี้ไม่จำเป็นต้องเลวร้าย (จริง ๆ แล้วมันมีประโยชน์มาก: AB ไม่จำเป็นต้องได้รับการปรับปรุงอย่างชัดเจนเพื่อให้ตัวเองตรงกันกับทั้ง A และ B) แต่ก็เป็นพฤติกรรมที่ต้องระวัง ข้อยกเว้นที่สำคัญ: การปรับขนาด A และ / หรือ B ให้ใหญ่ขึ้นอาจทำให้สิ่งเหล่านี้ถูกจัดสรรใหม่ในหน่วยความจำ (สำหรับความต้องการพื้นที่ต่อเนื่อง) และสิ่งนี้จะทำให้ AB ใช้ไม่ได้
  • เนื่องจากทุกการเข้าถึงองค์ประกอบถูกนำหน้าด้วยการทดสอบ (กล่าวคือ "i <v1.size ()") เวลาในการเข้าถึง VecProxy แม้ว่าค่าคงที่จะค่อนข้างช้ากว่าเวกเตอร์อีกเล็กน้อย
  • วิธีการนี้สามารถสรุปได้ทั่วไปถึง n เวกเตอร์ ฉันไม่ได้ลอง แต่มันไม่ควรเป็นเรื่องใหญ่

2

อีกหนึ่งตัวแปรที่เรียบง่ายซึ่งยังไม่ได้กล่าวถึง:

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

และการใช้อัลกอริทึมผสาน:

#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

หากเวกเตอร์ของคุณถูกจัดเรียง * ตรวจสอบset_unionจาก <algorithm>

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

มีตัวอย่างที่ละเอียดกว่านี้ในลิงค์

* ขอบคุณ rlbond


4
นอกจากนี้มันไม่ได้ทำสิ่งเดียวกันกับการผนวกโดยตรง - องค์ประกอบในช่วงเอาต์พุตไม่ซ้ำกันซึ่งอาจไม่ใช่สิ่งที่ OP ต้องการ (พวกเขาอาจไม่สามารถเทียบเคียงได้) แน่นอนว่ามันไม่ใช่วิธีที่มีประสิทธิภาพที่สุดในการทำเช่นนั้น
ปีเตอร์

-1

โซลูชันทั้งหมดนั้นถูกต้อง แต่ฉันพบว่าง่ายขึ้นเพียงแค่เขียนฟังก์ชันเพื่อใช้สิ่งนี้ แบบนี้:

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

ด้วยวิธีนี้คุณสามารถหลีกเลี่ยงตำแหน่งชั่วคราวเช่นนี้:

ContainerInsert(vec, GetSomeVector());
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.