C++ 11 offre plusieurs méthodes pour parcourir les conteneurs. Par exemple:
for(auto c : container) fun(c)
for_each(container.begin(),container.end(),fun)
Cependant, quelle est la méthode recommandée pour effectuer une itération sur deux conteneurs (ou plus) de la même taille afin de réaliser quelque chose comme:
for(unsigned i = 0; i < containerA.size(); ++i) {
containerA[i] = containerB[i];
}
Plutôt tard pour la fête. Mais: je voudrais itérer sur les indices. Mais pas avec la boucle for
classique, mais avec une boucle for
basée sur une plage et sur les index:
for(unsigned i : indices(containerA)) {
containerA[i] = containerB[i];
}
indices
est une fonction d'encapsulation simple qui renvoie une plage (évaluée paresseuse) pour les index. Puisque la mise en œuvre - bien que simple - est un peu trop longue pour la poster ici, vous pouvez trouver une implémentation sur GitHub .
Ce code est aussi efficace que si vous utilisiez une boucle manuelle for
classique.
Si ce modèle se produit souvent dans vos données, envisagez d'utiliser un autre modèle qui Zip
s deux séquences et produit une plage de n-uplets, correspondant aux éléments appariés:
for (auto& [a, b] : Zip(containerA, containerB)) {
a = b;
}
L'implémentation de Zip
reste un exercice pour le lecteur, mais elle découle facilement de l'implémentation de indices
.
(Avant C++ 17, vous deviez écrire le texte suivant :)
for (auto items&& : Zip(containerA, containerB))
get<0>(items) = get<1>(items);
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 Boost.Iterator, avec une petite fonction pour le rendre utilisable dans les boucles for basées sur la plage. Dans la plupart des cas, cela fonctionnera:
template<class... Conts>
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";
Cependant, pour une généricité complète, vous voulez probablement quelque chose de plus semblable à this , qui fonctionnera correctement pour les tableaux et les types définis par l'utilisateur qui n'ont pas de membre begin()
end()
mais _/do ont begin
/end
fonctions dans leur espace de noms. Cela permettra également à l'utilisateur d'obtenir spécifiquement l'accès const
à travers les fonctions Zip_c...
.
Et si vous êtes un partisan des messages d'erreur Nice, comme moi, vous voudrez probablement this , qui vérifie si des conteneurs temporaires ont été passés à l'une des fonctions Zip_...
et affiche un message d'erreur Nice, le cas échéant.
je me demande pourquoi personne ne l'a mentionné:
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 les tailles de conteneur ne correspondent pas, vous devrez alors insérer le code dans les instructions if.
Il y a beaucoup de façons de faire des choses spécifiques avec plusieurs conteneurs, comme indiqué dans l'en-tête algorithm
. Par exemple, dans l'exemple que vous avez donné, vous pouvez utiliser std::copy
au lieu d'une boucle for explicite.
En revanche, il n’existe pas de méthode intégrée permettant d’itérer de manière générique plusieurs conteneurs autres qu’une boucle for normale. Cela n’est pas surprenant, car il existe beaucoup de façons d’itérer. Pensez-y: vous pouvez parcourir un conteneur en une étape, un conteneur en une autre; ou à travers un conteneur jusqu'à la fin puis commencez à insérer pendant que vous allez à 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 ...
Toutefois, si vous souhaitez créer votre fonction de style own "for_each" qui itère dans deux conteneurs uniquement jusqu'à la longueur du plus court, vous pouvez procéder de la manière suivante:
template <typename Container1, typename Container2>
void custom_for_each(
Container1 &c1,
Container2 &c2,
std::function<void(Container1::iterator &it1, Container2::iterator &it2)> 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 n'importe quelle stratégie d'itérations de la même manière.
Bien sûr, vous pouvez faire valoir qu'il est plus facile de faire la boucle for interne directement 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 la bonne chose est que cela est très réutilisable. =)
Dans le cas où vous devez itérer simultanément sur 2 conteneurs uniquement, il existe une version étendue de l'algorithme standard for_each dans la bibliothèque de plage amplifiée, par exemple:
#include <vector>
#include <boost/assign/list_of.hpp>
#include <boost/bind.hpp>
#include <boost/range/algorithm_ext/for_each.hpp>
void foo(int a, int& b)
{
b = a + 1;
}
int main()
{
std::vector<int> contA = boost::assign::list_of(4)(3)(5)(2);
std::vector<int> 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 gérer plus de 2 conteneurs dans un algorithme, vous devez jouer avec Zip.
Une bibliothèque de gammes fournit ceci et d'autres fonctionnalités très utiles. L'exemple suivant utilise Boost.Range . Rangev3 d'Eric Niebler devrait être une bonne alternative.
#include <boost/range/combine.hpp>
#include <iostream>
#include <vector>
#include <list>
int main(int, const char*[])
{
std::vector<int> const v{0,1,2,3,4};
std::list<char> 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 le rendra encore meilleur avec les liaisons structurées:
int main(int, const char*[])
{
std::vector<int> const v{0,1,2,3,4};
std::list<char> const l{'a', 'b', 'c', 'd', 'e'};
for(auto const& [ti, tc]: boost::combine(v, l))
{
std::cout << '(' << ti << ',' << tc << ')' << '\n';
}
return 0;
}
une autre solution pourrait consister à capturer une référence de l'itérateur de l'autre conteneur dans un lambda et à utiliser l'opérateur post increment sur celui-ci. par exemple, une copie simple serait:
vector<double> a{1, 2, 3};
vector<double> 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
et l’incrémenter. Cela s’étend facilement au cas de conteneurs multiples.
Voici une variante
template<class ... Iterator>
void increment_dummy(Iterator ... i)
{}
template<class Function,class ... Iterator>
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 utiliser ceci (fonction variadique de style C):
template<typename T>
void foreach(std::function<void(T)> callback, int count...) {
va_list args;
va_start(args, count);
for (int i = 0; i < count; i++) {
std::vector<T> v = va_arg(args, std::vector<T>);
std::for_each(v.begin(), v.end(), callback);
}
va_end(args);
}
foreach<int>([](const int &i) {
// do something here
}, 6, vecA, vecB, vecC, vecD, vecE, vecF);
ou ceci (en utilisant un pack de paramètres de fonction):
template<typename Func, typename T>
void foreach(Func callback, std::vector<T> &v) {
std::for_each(v.begin(), v.end(), callback);
}
template<typename Func, typename T, typename... Args>
void foreach(Func callback, std::vector<T> &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 entre accolades):
template<typename Func, typename T>
void foreach(Func callback, std::initializer_list<std::vector<T>> 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 rejoindre des vecteurs comme ici: Quel est le meilleur moyen de concaténer deux vecteurs? et ensuite itérer sur un gros vecteur.