Qual è il modo migliore per concatenare due vettori?


190

Sto usando il multitreading e voglio unire i risultati. Per esempio:

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

Voglio che AB abbia i contenuti di A e i contenuti di B in quell'ordine. Qual è il modo più efficiente di fare qualcosa del genere?


1
Se si cerca efficienza quando si lavora con contenitori di grandi dimensioni, potrebbe essere più efficiente utilizzare l'elenco, in cui è possibile unire l'un l'altro con diverse operazioni del puntatore. Ma l'elenco ha un sovraccarico di spazio (considerare l'utilizzo di un singolo elenco collegato).
Kemin Zhou

Risposte:


323
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
Grazie! Non avrei pensato alla riserva.
jmasterx,

10
dovrebbe copiare ogni elemento, quindi è O (n)
Kirill V. Lyadvinsky l'

1
Non sei sicuro di fare una nuova domanda o no, ma questa risposta può essere migliorata quando si tiene conto della semantica di spostamento? C'è un modo in cui posso aspettarmi / istruire il compilatore a fare un singolo spostamento di memoria invece di eseguire il ciclo su tutti gli elementi?
Broes De Cat,

2
@boycy No. È tempo costante ammortizzato di respingere un elemento. Per respingere n elementi è O (n)
Konrad Lindenbach

1
@Konrad Non ho implicato diversamente, ma grazie per il chiarimento. Nota la complessità di un'operazione di inserimento non è mai data in termini di numero di elementi da inserire - che fornirà sempre O (n) - ma in termini di numero di elementi già presenti nel contenitore, poiché ciò fornisce una misura della sua scalabilità .
boycy

65

Questo è esattamente ciò che la funzione membro std::vector::insertè per

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

4
@ Nick: lento rispetto a cosa?
GManNickG

2
Forse che controlla abbastanza spazio su ogni inserto di elemento? L'uso anticipato di riserva lo accelererà.
RvdK,

10
@Nick: Non sarei sorpreso se ogni moderna implementazione stdlib fosse specializzata insertin iteratori ad accesso casuale e riservata in anticipo.
GManNickG

1
@Gman: Questo è un punto giusto poiché sappiamo che la fonte è anche un vettore (dove iteratore distanceha una complessità O (1)). Tuttavia, le garanzie prestazionali insertsono qualcosa di cui tenere conto quando spesso si può fare di meglio pianificando in anticipo.
Nick Bastin,

2
@RvdK per verificare lo spazio sono solo alcune istruzioni: capacità di carico, confronto con dimensioni, salto condizionale; tutto ciò è un costo trascurabile per la maggior parte dei casi. Dalla size < capacitymaggior parte delle volte, la previsione della diramazione probabilmente farà sì che le istruzioni della diramazione non riallocata siano nella pipeline delle istruzioni, riducendo al minimo la latenza indotta dalla diramazione eccetto per un conteggio di iterazione basso. Ciò presuppone una buona implementazione vettoriale, oltre alla pipeline di istruzioni della CPU e alla previsione del ramo [buona], ma questi sono presupposti abbastanza affidabili per una moderna toolchain e una macchina desktop. Non so degli smartphone però ...
boicoso il

27

Dipende se hai davvero bisogno di concatenare fisicamente i due vettori o se vuoi dare l'apparenza di concatenazione per amore dell'iterazione. La funzione boost :: join

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

ti darò questo.

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

dovrebbe darti

1
2
3
4
5
6

Nota boost :: join non copia i due vettori in un nuovo contenitore ma genera una coppia di iteratori (intervallo) che coprono l'intervallo di entrambi i contenitori. Ci sarà un certo sovraccarico di prestazioni ma forse meno che copiare prima tutti i dati in un nuovo contenitore.


1
Bella idea Dopo aver pensato per un po 'ho capito che questo obiettivo può essere raggiunto anche senza usare le librerie boost. Ho pubblicato una risposta che spiega come.
Ronald Souza,

11

Basato sulla risposta di Kiril V. Lyadvinsky , ho realizzato una nuova versione. Questo frammento utilizza template e overload. Con esso, puoi scrivere vector3 = vector1 + vector2e vector4 += vector3. Spero possa essere d'aiuto.

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
Intendi aggiungere gli elementi di ciascun vettore l'uno all'altro? O intendi aggiungere? Questo è chiaro ora ma per i prossimi 5 anni ..? Non è necessario sovraccaricare l'operatore se il significato è ambiguo.
SR

2
@SR intendo concedere. Ho scritto questa risposta 3 anni fa. So ancora cosa significa. Nessun problema lì. Se il C ++ potesse fornire il proprio sovraccarico, sarebbe ancora meglio. (e sì, ::viene preso;)
aloisdg si trasferisce su codidact.com il

Sicuramente non chiaro in generale che v1 + v2non rappresenta l'addizione.
Apollys supporta Monica il


L'alternativa sarebbe usare @come in F #
aloisdg trasferirsi su codidact.com il

6

Nella direzione della risposta di Bradgonesurfing, molte volte non è necessario concatenare due vettori (O (n)), ma invece lavorare con loro come se fossero concatenati (O (1)) . Se questo è il tuo caso, può essere fatto senza la necessità di librerie Boost.

Il trucco è creare un proxy vettoriale: una classe wrapper che manipola i riferimenti ad entrambi i vettori, visti esternamente come uno singolo, contiguo.

USO

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

IMPLEMENTAZIONE

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

VANTAGGI PRINCIPALI

È O (1) (tempo costante) per crearlo e con allocazione di memoria aggiuntiva minima.

ALCUNI PRODOTTI DA CONSIDERARE

  • Dovresti provarlo solo se sai davvero cosa stai facendo quando hai a che fare con i riferimenti . Questa soluzione è intesa per lo scopo specifico della domanda posta, per la quale funziona abbastanza bene . Impiegarlo in qualsiasi altro contesto può portare a comportamenti imprevisti se non si è sicuri del funzionamento dei riferimenti.
  • In questo esempio, AB non fornisce un operatore di accesso non const ([]). Sentiti libero di includerlo, ma tieni a mente: poiché AB contiene riferimenti, assegnargli i valori influenzerà anche gli elementi originali in A e / o B. Se questa è una caratteristica desiderabile o meno, è una domanda specifica dell'applicazione che dovresti considerare attentamente.
  • Qualsiasi modifica apportata direttamente ad A o B (come l'assegnazione di valori, l'ordinamento, ecc.) "Modifica" anche AB. Questo non è necessariamente negativo (in realtà, può essere molto utile: AB non ha mai bisogno di essere aggiornato esplicitamente per mantenersi sincronizzato con A e B), ma è certamente un comportamento di cui bisogna essere consapevoli. Eccezione importante: ridimensionare A e / o B su sth più grande può comportare la riallocazione di questi in memoria (per la necessità di spazio contiguo) e ciò a sua volta invaliderebbe AB.
  • Poiché ogni accesso a un elemento è preceduto da un test (ovvero "i <v1.size ()"), il tempo di accesso a VecProxy, sebbene costante, è anche un po 'più lento di quello dei vettori.
  • Questo approccio può essere generalizzato a n vettori. Non ho provato, ma non dovrebbe essere un grosso problema.

2

Un'altra variante semplice che non è stata ancora menzionata:

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

E usando l'algoritmo di unione:

#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

Se i tuoi vettori sono ordinati *, controlla set_union da <algorithm>.

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

C'è un esempio più approfondito nel link

* grazie rlbond


4
inoltre, non fa la stessa cosa di un'appendice semplice: gli elementi nell'intervallo di output sono unici, il che potrebbe non essere quello che voleva l'OP (potrebbero anche non essere comparabili). Non è certamente il modo più efficiente di farlo.
Peter,

-1

Tutte le soluzioni sono corrette, ma ho trovato più semplice scrivere una funzione per implementarlo. come questo:

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

In questo modo puoi evitare il posizionamento temporaneo come questo:

ContainerInsert(vec, GetSomeVector());
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.