Quelle est la meilleure façon de concaténer deux vecteurs?


196

J'utilise le multitreading et je souhaite fusionner les résultats. Par exemple:

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

Je veux qu'AB ait le contenu de A et le contenu de B dans cet ordre. Quelle est la manière la plus efficace de faire quelque chose comme ça?


1
Si vous recherchez l'efficacité lorsque vous travaillez avec des conteneurs de grande taille, il peut être plus efficace d'utiliser la liste, dans laquelle vous pouvez les assembler avec plusieurs opérations de pointeur. Mais la liste a une surcharge d'espace (envisagez d'utiliser une seule liste chaînée).
Kemin Zhou

Réponses:


338
AB.reserve( A.size() + B.size() ); // preallocate memory
AB.insert( AB.end(), A.begin(), A.end() );
AB.insert( AB.end(), B.begin(), B.end() );

7
Merci! Je n'aurais pas pensé à la réserve.
jmasterx

11
il devrait copier chaque élément, donc c'est O (n)
Kirill V. Lyadvinsky

1
Vous ne savez pas s'il faut poser une nouvelle question ou non, mais cette réponse peut-elle être améliorée en prenant en compte la sémantique du déplacement? Y a-t-il un moyen que je puisse espérer / demander au compilateur de faire un seul déplacement de mémoire au lieu de boucler sur tous les éléments?
Broes De Cat

2
@boycy Non. Il est amorti à temps constant pour repousser un élément. Repousser n éléments est O (n)
Konrad Lindenbach

1
@Konrad Je n'ai pas laissé entendre le contraire, mais merci pour la clarification. Notez que la complexité d'une opération d'insertion n'est jamais donnée en termes de nombre d'éléments insérés - ce qui donnera toujours O (n) - mais en termes de nombre d'éléments déjà dans le conteneur, car cela fournit une mesure de son évolutivité .
boycy

71

C'est précisément à cela que std::vector::insertsert la fonction membre

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

4
@Nick: Lent par rapport à quoi?
GManNickG

2
Peut-être qu'il vérifie suffisamment d'espace sur chaque insert d'élément? L'utilisation préalable de la réserve accélérera le processus.
RvdK

10
@Nick: Je ne serais pas surpris si chaque implémentation moderne de stdlib se spécialisait insertsur les itérateurs à accès aléatoire et était réservée à l'avance.
GManNickG

1
@Gman: C'est un bon point car nous savons que la source est aussi un vecteur (où l'itérateur distancea une complexité O (1)). Néanmoins, les garanties de performance de insertsont quelque chose dont il faut tenir compte lorsque vous pouvez souvent faire mieux en planifiant à l'avance.
Nick Bastin

2
@RvdK vérifier l'espace n'est que quelques instructions: capacité de charge, comparaison à la taille, saut conditionnel; tout cela est un coût négligeable dans la plupart des cas. Étant donné que la size < capacityplupart du temps, la prédiction de branchement entraînera probablement le fait que les instructions de la branche non réallouées se trouvent dans le pipeline d'instructions, minimisant la latence induite par la branche, sauf pour un faible nombre d'itérations. Cela suppose une bonne implémentation vectorielle, plus un pipeline d'instructions CPU et une [bonne] prédiction de branche, mais ce sont des hypothèses assez fiables pour une chaîne d'outils et une machine de bureau modernes. Je ne sais pas sur les smartphones cependant ..
boycy

27

Cela dépend si vous avez vraiment besoin de concaténer physiquement les deux vecteurs ou que vous voulez donner l'apparence d'une concaténation pour le plaisir de l'itération. La fonction boost :: join

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

vous donnera ceci.

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

devrait te donner

1
2
3
4
5
6

Remarque boost :: join ne copie pas les deux vecteurs dans un nouveau conteneur mais génère une paire d'itérateurs (plage) qui couvrent l'étendue des deux conteneurs. Il y aura une surcharge de performances, mais peut-être moins que de copier d'abord toutes les données dans un nouveau conteneur.


1
Bonne idée. Après avoir réfléchi pendant un moment, j'ai réalisé que cet objectif pouvait également être atteint sans utiliser de bibliothèques boost. J'ai posté une réponse expliquant comment.
Ronald Souza

11

Sur la base de la réponse de Kiril V. Lyadvinsky , j'ai fait une nouvelle version. Cet extrait utilise un modèle et une surcharge. Avec lui, vous pouvez écrire vector3 = vector1 + vector2et vector4 += vector3. J'espère que cela peut vous aider.

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
Voulez-vous ajouter les éléments de chaque vecteur les uns aux autres? Ou vous voulez ajouter? C'est clair maintenant mais pour les 5 prochaines années ..? Vous ne devez pas surcharger l'opérateur si la signification est ambiguë.
SR

2
@SR Je veux concerter. J'ai écrit cette réponse il y a 3 ans. Je sais toujours ce que cela signifie. Aucun problème là-bas. Si C ++ pouvait fournir sa propre surcharge, ce sera encore mieux. (et oui ::est pris;)
aloisdg passe à codidact.com

Certainement pas clair en général qui v1 + v2ne représente pas l'addition.
Apollys soutient Monica


Une alternative serait d'utiliser @comme en F #
aloisdg se déplaçant sur codidact.com

6

Dans le sens de la réponse de Bradgonesurfing, souvent, on n'a pas vraiment besoin de concaténer deux vecteurs (O (n)), mais plutôt de travailler avec eux comme s'ils étaient concaténés (O (1)) . Si tel est votre cas, cela peut être fait sans avoir besoin des bibliothèques Boost.

L'astuce consiste à créer un proxy vectoriel: une classe wrapper qui manipule les références aux deux vecteurs, vu extérieurement comme un seul et contigu.

USAGE

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

LA MISE EN OEUVRE

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

PRINCIPAL AVANTAGE

C'est O (1) (temps constant) pour le créer, et avec une allocation de mémoire supplémentaire minimale.

QUELQUES CHOSES À CONSIDÉRER

  • Vous ne devriez y aller que si vous savez vraiment ce que vous faites en matière de références . Cette solution est destinée à l'objectif spécifique de la question posée, pour laquelle elle fonctionne plutôt bien . L'employer dans tout autre contexte peut entraîner un comportement inattendu si vous n'êtes pas sûr du fonctionnement des références.
  • Dans cet exemple, AB ne fournit pas d' opérateur d'accès non const ([]). N'hésitez pas à l'inclure, mais gardez à l'esprit: puisque AB contient des références, lui attribuer des valeurs affectera également les éléments d'origine dans A et / ou B.Que ce soit une fonctionnalité souhaitable ou non, c'est une question spécifique à l'application, il faut réfléchissez bien.
  • Toutes les modifications apportées directement à A ou B (comme l'attribution de valeurs, le tri, etc.) "modifieront" également AB. Ce n'est pas nécessairement mauvais (en fait, cela peut être très pratique: AB n'a jamais besoin d'être explicitement mis à jour pour rester synchronisé avec A et B), mais c'est certainement un comportement dont il faut être conscient. Exception importante: redimensionner A et / ou B à qch plus grand peut conduire à une réallocation en mémoire (pour le besoin d'espace contigu), ce qui à son tour invaliderait AB.
  • Parce que chaque accès à un élément est précédé d'un test (à savoir, "i <v1.size ()"), le temps d'accès à VecProxy, bien que constant, est également un peu plus lent que celui des vecteurs.
  • Cette approche peut être généralisée à n vecteurs. Je n'ai pas essayé, mais ça ne devrait pas être un gros problème.

2

Une variante plus simple qui n'a pas encore été mentionnée:

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

Et en utilisant l'algorithme de fusion:

#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

Si vos vecteurs sont triés *, vérifiez set_union depuis <algorithm>.

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

Il y a un exemple plus complet dans le lien

* merci rlbond


4
De plus, il ne fait pas la même chose qu'un ajout direct - les éléments de la plage de sortie sont uniques, ce qui peut ne pas être ce que l'OP voulait (ils pourraient même ne pas être comparables). Ce n'est certainement pas la manière la plus efficace de le faire.
Peter

-1

Toutes les solutions sont correctes, mais j'ai trouvé plus simple d'écrire simplement une fonction pour l'implémenter. comme ça:

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

De cette façon, vous pouvez éviter le placement temporaire comme ceci:

ContainerInsert(vec, GetSomeVector());
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.