İki vektörü birleştirmenin en iyi yolu nedir?


189

Multitreading kullanıyorum ve sonuçları birleştirmek istiyorum. Örneğin:

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

AB'nin A ve B içeriklerini bu sırayla almasını istiyorum. Böyle bir şey yapmanın en etkili yolu nedir?


1
Büyük boyutlu kaplarla çalışırken verimlilik arıyorsanız, birkaç işaretçi işlemiyle birini diğerine ekleyebileceğiniz listeyi kullanmak daha verimli olabilir. Ancak listenin ek yükü vardır (tek bağlantılı liste kullanmayı düşünün).
Kemin Zhou

Yanıtlar:


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
Teşekkürler! Rezerv düşünemezdim.
jmasterx

10
her öğeyi kopyalamalıdır, bu yüzden O (n)
Kirill V. Lyadvinsky

1
Yeni bir soru sorulup sorulmayacağından emin değilsiniz, ancak hareket semantiği dikkate alındığında bu cevap geliştirilebilir mi? Derleyiciye tüm öğeler üzerinde döngü yapmak yerine tek bir bellek hareketi yapmasını bekleyebileceğim / talimat verebileceğim bir yol var mı?
Broes De Cat

2
Bir öğeyi geri itmek için sabit bir süre amortismana tabi tutulur. N elemanını geri itmek için O (n)
Konrad Lindenbach

1
@Konrad Başka türlü ima etmedim, ama açıklama için teşekkürler. Bir ekleme işlemi karmaşıklığı sokulması elemanların sayısı bakımından verilen hiç Not - ki her zaman olacaktır vermek O (n) - ama elemanların sayısı açısından zaten onun ölçeklenebilirlik bir ölçüsünü sağlar çünkü kap, .
boycy

65

Üye işlevi tam olarak std::vector::insertbunun içindir

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

4
@Nick: Neye kıyasla yavaş?
GManNickG

2
Belki de her bir eleman için yeterli alan olup olmadığını kontrol eder? Rezervin önceden kullanılması onu hızlandıracaktır.
RvdK

10
@Nick: Her modern stdlib uygulaması insertrastgele erişimli yineleyiciler üzerinde uzmanlaştıysa ve ön tarafa ayrılmışsa şaşırmam .
GManNickG

1
@Gman: Bu adil bir nokta çünkü kaynağın da bir vektör olduğunu biliyoruz (yineleyicinin distanceO (1) karmaşıklığı olduğu). Yine de, performans garantileri, insertönceden planlama yaparak genellikle daha iyi yapabileceğiniz zamanlara dikkat etmeniz gereken bir şeydir.
Nick Bastin

2
@RvdK alan kontrolü sadece birkaç talimattır: yük kapasitesi, boyutla karşılaştır, koşullu atlama; bunların çoğu çoğu durumda ihmal edilebilir bir maliyettir. Yana size < capacityçoğu zaman, dallanma tahmini muhtemel olmayan yeniden tahsis şubesinin talimatları düşük yineleme sayısı hariç şube kaynaklı gecikme minimize, talimat boru hattı olmak olacaktır çünkü. Bu, iyi bir vektör uygulaması ve ayrıca CPU talimatı boru hattı ve [iyi] şube tahmini olduğunu varsayar, ancak bunlar modern bir takım zinciri ve masaüstü makinesi için oldukça güvenilir varsayımlardır. Akıllı telefonlar hakkında bilmiyorum ..
boycy

27

İki vektörü gerçekten fiziksel olarak birleştirmeniz gerekip gerekmediğine veya yineleme uğruna birleştirme görünümü vermek isteyip istemediğinize bağlıdır. Boost :: birleştirme işlevi

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

bunu size verecek.

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

sana vermeliyim

1
2
3
4
5
6

Notu: join, iki vektörü yeni bir kaba kopyalamaz, ancak her iki kabın aralığını kapsayan bir çift yineleyici (aralık) oluşturur. Bazı performans yükleri olacak, ancak önce tüm verileri yeni bir kapsayıcıya kopyalamaktan daha az olacaktır.


1
İyi fikir. Bir süre düşündükten sonra, bu hedefin destek kütüphaneleri kullanılmadan da gerçekleştirilebileceğini fark ettim. Nasıl olduğunu açıklayan bir cevap gönderdim.
Ronald Souza

11

Kiril V. Lyadvinsky'nin cevabına dayanarak yeni bir versiyon yaptım. Bu pasaj şablonu ve aşırı yükleme kullanır. Bununla beraber vector3 = vector1 + vector2ve yazabilirsiniz vector4 += vector3. Umarım yardımcı olabilir.

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
Her vektörün elemanlarını birbirine mi eklemek istiyorsun? Yoksa eklemek mi istiyorsun? Bu şimdi açık ama önümüzdeki 5 yıl için ..? Anlam belirsizse operatörü aşırı yüklememelisiniz.
SR

2
@SR Demek istiyorum. Bu cevabı 3 yıl önce yazdım. Hala ne anlama geldiğini biliyorum. Sorun yok. C ++ kendi aşırı yüklenmesini sağlayabilirse daha da iyi olacaktır. (ve evet ::alınır;)
aloisdg hareketli codidact.com

Kesinlikle genel olarak v1 + v2ek temsil etmez açık değil.
Apollys,


Alternatif @F # gibi kullanmak olacaktır
aloisdg taşınma codidact.com

6

Bradgonesurfing'in cevabı yönünde, çoğu zaman bir kişinin iki vektörü (O (n)) birleştirmesi gerekmez , bunun yerine sadece birleştirilmiş gibi çalışırlar (O (1)) . Bu sizin durumunuzsa, Boost kütüphanelerine ihtiyaç duymadan yapılabilir.

İşin püf noktası, bir vektör proxy oluşturmaktır: harici olarak tek bir bitişik olarak görülen her iki vektöre yapılan referansları işleyen bir sarıcı sınıf .

KULLANIM

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

UYGULAMA

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

ANA FAYDA

Onu oluşturmak için O (1) (sabit zaman) ve minimum ekstra bellek ayırma.

DİKKAT EDİLECEK BAZI ÖĞELER

  • Sadece referanslarla uğraşırken ne yaptığınızı gerçekten biliyorsanız, bunu yapmalısınız . Bu çözüm, oldukça iyi çalıştığı sorunun özel amacı için tasarlanmıştır . Başka bir bağlamda kullanmak, referansların nasıl çalıştığından emin değilseniz beklenmedik davranışlara yol açabilir.
  • Bu örnekte, AB etmez olmayan bir const olmayan erişim operatöre ([]). Bunu eklemekten çekinmeyin, ancak unutmayın: AB referanslar içerdiğinden, değerleri atamak da A ve / veya B içindeki orijinal öğeleri etkileyecektir. Bu istenen bir özellik olsun ya da olmasın, bu uygulamaya özgü bir soru dikkatlice düşünün.
  • Doğrudan A veya B'de yapılan değişiklikler (değer atama, sıralama vb. Gibi) de AB'yi "değiştirir". Bu mutlaka kötü değildir (aslında, çok kullanışlı olabilir: AB'nin kendisini hem A hem de B ile senkronize tutmak için asla açıkça güncellenmesine gerek yoktur), ancak kesinlikle bir kişinin farkında olması gereken bir davranıştır. Önemli istisna: A ve / veya B'yi daha büyük boyuta yeniden boyutlandırmak , bunların bellekte yeniden tahsis edilmesine yol açabilir (bitişik alan ihtiyacı için) ve bu da AB'yi geçersiz kılar.
  • Bir öğeye her erişimden önce bir test (yani, "i <v1.size ()") olduğu için, VecProxy erişim süresi, sabit olmasına rağmen, vektörlerden biraz daha yavaştır.
  • Bu yaklaşım n vektörlerine genelleştirilebilir. Denemedim, ama çok önemli olmamalı.

2

Henüz bahsedilmeyen bir basit varyant:

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

Ve birleştirme algoritması kullanarak:

#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

Vektörleriniz sıralanırsa *, <algoritma> 'dan set_union ' a bakın.

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

Bağlantıda daha ayrıntılı bir örnek var

* teşekkürler rlbond


4
Ayrıca, düz bir ekleme ile aynı şeyi yapmaz - çıkış aralığındaki öğeler benzersizdir, bu OP'nin istediği şey olmayabilir (karşılaştırılabilir bile olmayabilir). Kesinlikle bunu yapmanın en etkili yolu değil.
Peter

-1

Tüm çözümler doğrudur, ancak bunu uygulamak için bir işlev yazmayı daha kolay buldum. bunun gibi:

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

Bu şekilde geçici yerleşimi şu şekilde önleyebilirsiniz:

ContainerInsert(vec, GetSomeVector());
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.