Quelle est la meilleure façon de parcourir deux ou plusieurs conteneurs simultanément?

C ++ 11 fournit plusieurs façons d’itérer sur des conteneurs. Par exemple:

Boucle basée sur la plage

for(auto c : container) fun(c) 

std :: for_each

 for_each(container.begin(),container.end(),fun) 

Cependant, quelle est la méthode recommandée pour itérer sur deux (ou plus) conteneurs de même taille pour accomplir quelque chose comme:

 for(unsigned i = 0; i < containerA.size(); ++i) { containerA[i] = containerB[i]; } 

Pour votre exemple spécifique, utilisez simplement

 std::copy_n(contB.begin(), contA.size(), contA.begin()) 

Pour le cas plus général, vous pouvez utiliser le zip_iterator de zip_iterator , avec une petite fonction pour le rendre utilisable dans les boucles basées sur des plages. Dans la plupart des cas, cela fonctionnera:

 template auto zip_range(Conts&... conts) -> decltype(boost::make_iterator_range( boost::make_zip_iterator(boost::make_tuple(conts.begin()...)), boost::make_zip_iterator(boost::make_tuple(conts.end()...)))) { return {boost::make_zip_iterator(boost::make_tuple(conts.begin()...)), boost::make_zip_iterator(boost::make_tuple(conts.end()...))}; } // ... for(auto&& t : zip_range(contA, contB)) std::cout << t.get<0>() << " : " << t.get<1>() << "\n"; 

Exemple en direct.

Cependant, pour une générosité à part entière, vous voudrez probablement quelque chose de plus, qui fonctionnera correctement pour les tableaux et les types définis par l'utilisateur qui n'ont pas de membre begin() / end() mais ont des fonctions begin / end dans leur espace de noms . En outre, cela permettra à l'utilisateur d'obtenir spécifiquement un access const aux fonctions zip_c...

Et si vous êtes un partisan des messages d'erreur sympas, comme moi, alors vous voudrez probablement ceci , qui vérifie si des conteneurs temporaires ont été passés à l'une des fonctions zip_... , et imprime un message d'erreur si c'est le cas.

Plutôt tard à la fête. Mais: je réitérerais sur les indices. Mais pas avec le classique for boucle mais avec une boucle basée for boucle sur les index:

 for(unsigned i : indices(containerA)) containerA[i] = containerB[i]; 

indices est une fonction de wrapper simple qui retourne une plage (évaluée paresseuse) pour les index. Comme l’implémentation – bien que simple – est un peu longue pour la publier ici, vous pouvez trouver une implémentation sur GitHub .

Ce code est aussi efficace que d’utiliser un manuel, classique for boucle.

Si ce motif se produit souvent dans vos données, envisagez d’utiliser un autre modèle qui zip deux séquences et produit une plage de tuples, correspondant aux éléments associés:

 for (auto items&& : zip(containerA, containerB)) get<0>(items) = get<1>(items); 

L’implémentation de zip est laissée comme un exercice pour le lecteur, mais elle découle facilement de la mise en place d’ indices .

Je me demande pourquoi personne n’a mentionné ceci:

 auto ItA = VectorA.begin(); auto ItB = VectorB.begin(); while(ItA != VectorA.end() || ItB != VectorB.end()) { if(ItA != VectorA.end()) { ++ItA; } if(ItB != VectorB.end()) { ++ItB; } } 

PS: si la taille du conteneur ne correspond pas, vous devrez placer le code dans les instructions if.

Il existe de nombreuses façons de faire des choses spécifiques avec plusieurs conteneurs, comme indiqué dans l’ algorithm tête de l’ algorithm . Par exemple, dans l’exemple que vous avez donné, vous pouvez utiliser std::copy au lieu d’une boucle explicite.

D’un autre côté, il n’y a pas de manière intégrée d’itérer de manière générique plusieurs conteneurs autres qu’une boucle normale. Ce n’est pas surprenant car il y a beaucoup de façons d’itérer. Pensez-y: vous pouvez parcourir un conteneur avec une étape, un conteneur avec une autre étape; ou à travers un conteneur jusqu’à ce qu’il arrive à la fin, puis commencez à insérer pendant que vous passez à la fin de l’autre conteneur; ou une étape du premier conteneur à chaque fois que vous traversez complètement l’autre conteneur, puis recommencez; ou un autre motif; ou plus de deux conteneurs à la fois; etc …

Cependant, si vous vouliez créer votre propre fonction de style “for_each” qui ne traverse que deux conteneurs jusqu’à la plus courte, vous pourriez faire quelque chose comme ceci:

 template  void custom_for_each( Container1 &c1, Container2 &c2, std::function f) { Container1::iterator begin1 = c1.begin(); Container2::iterator begin2 = c2.begin(); Container1::iterator end1 = c1.end(); Container2::iterator end2 = c2.end(); Container1::iterator i1; Container1::iterator i2; for (i1 = begin1, i2 = begin2; (i1 != end1) && (i2 != end2); ++it1, ++i2) { f(i1, i2); } } 

De toute évidence, vous pouvez créer tout type de stratégie d’itérations de la même manière.

Bien sûr, vous pourriez faire valoir que le fait de faire directement la boucle interne est plus facile que d’écrire une fonction personnalisée comme celle-ci … et vous auriez raison si vous ne le faites qu’une ou deux fois. Mais ce qui est bien, c’est que c’est très réutilisable. =)

Dans le cas où vous avez besoin d’itérer simultanément sur 2 conteneurs seulement, il existe une version étendue de l’algorithme standard for_each dans la bibliothèque de plage de boost, par exemple:

 #include  #include  #include  #include  void foo(int a, int& b) { b = a + 1; } int main() { std::vector contA = boost::assign::list_of(4)(3)(5)(2); std::vector contB(contA.size(), 0); boost::for_each(contA, contB, boost::bind(&foo, _1, _2)); // contB will be now 5,4,6,3 //... return 0; } 

Lorsque vous devez manipuler plus de 2 conteneurs dans un seul algorithme, vous devez jouer avec zip.

une autre solution pourrait consister à capturer une référence de l’iterator de l’autre conteneur dans un lambda et à utiliser un opérateur de post-incrémentation à cet effet. Par exemple, une simple copie serait:

 vector a{1, 2, 3}; vector b(3); auto ita = a.begin(); for_each(b.begin(), b.end(), [&ita](auto &itb) { itb = *ita++; }) 

Dans lambda, vous pouvez faire n’importe quoi avec ita , puis l’incrémenter. Cela s’étend facilement au cas de plusieurs conteneurs.

Une bibliothèque de plages fournit ceci et d’autres fonctionnalités très utiles. L’exemple suivant utilise Boost.Range . Le rangev3 d’Eric Niebler devrait être une bonne alternative.

 #include  #include  #include  #include  int main(int, const char*[]) { std::vector const v{0,1,2,3,4}; std::list const l{'a', 'b', 'c', 'd', 'e'}; for(auto const& i: boost::combine(v, l)) { int ti; char tc; boost::tie(ti,tc) = i; std::cout << '(' << ti << ',' << tc << ')' << '\n'; } return 0; } 

C ++ 17 rendra cela encore meilleur avec les liaisons structurées:

 int main(int, const char*[]) { std::vector const v{0,1,2,3,4}; std::list const l{'a', 'b', 'c', 'd', 'e'}; for(auto const& [ti, tc]: boost::combine(v, l)) { std::cout << '(' << ti << ',' << tc << ')' << '\n'; } return 0; } 

Voici une variante

 template void increment_dummy(Iterator ... i) {} template void for_each_combined(size_t N,Function&& fun,Iterator... iter) { while(N!=0) { fun(*iter...); increment_dummy(++iter...); --N; } } 

Exemple d’utilisation

 void arrays_mix(size_t N,const float* x,const float* y,float* z) { for_each_combined(N,[](float x,float y,float& z){z=x+y;},x,y,z); } 

Je suis un peu en retard aussi; mais vous pouvez l’utiliser (fonction variadique de style C):

 template void foreach(std::function callback, int count...) { va_list args; va_start(args, count); for (int i = 0; i < count; i++) { std::vector v = va_arg(args, std::vector); std::for_each(v.begin(), v.end(), callback); } va_end(args); } foreach([](const int &i) { // do something here }, 6, vecA, vecB, vecC, vecD, vecE, vecF); 

ou ceci (en utilisant un pack de parameters de fonction):

 template void foreach(Func callback, std::vector &v) { std::for_each(v.begin(), v.end(), callback); } template void foreach(Func callback, std::vector &v, Args... args) { std::for_each(v.begin(), v.end(), callback); return foreach(callback, args...); } foreach([](const int &i){ // do something here }, vecA, vecB, vecC, vecD, vecE, vecF); 

ou ceci (en utilisant une liste d’initialisation entourée d’accolades):

 template void foreach(Func callback, std::initializer_list> list) { for (auto &vec : list) { std::for_each(vec.begin(), vec.end(), callback); } } foreach([](const int &i){ // do something here }, {vecA, vecB, vecC, vecD, vecE, vecF}); 

ou vous pouvez joindre des vecteurs comme ici: Quelle est la meilleure façon de concaténer deux vecteurs? puis itère sur le grand vecteur.