Existe-t-il un moyen d'itérer sur deux conteneurs (l'un suivi de l'autre), sans utiliser deux boucles for.
Mon intention est de faire quelque chose comme ça
1 2 3 4 5 6
pour imprimer
vector<int> a{ 1,2,3 }; vector<int> b{ 4,5,6 }; auto it = a.begin(); auto end = b.end(); for (; it != end; ++it) { if (it == a.end()) { it = b.begin(); } // do something with *it }
(bien sûr, cela ne fonctionne pas. L'explication se trouve dans ce réponse )
Je ne veux pas écrire deux boucles for et dupliquer le code à l'intérieur de la boucle.
Existe-t-il un moyen d'itérer sur a
suivi de b
avec une seule boucle for?
La seule chose à laquelle je peux penser est soit copier / déplacer le deuxième conteneur au premier ou créer un nouveau vecteur combinant a
et b
, puis itérer dessus. Je ne veux pas non plus faire cela, car cela entraînera des opérations de copie coûteuses.
9 Réponses :
Eh bien ... votre erreur est un double égal où vous avez besoin d'un seul égal.
Je veux dire, pas
if (it == a.end()) { it = b.begin(); } // ^ correct
mais
if (it == a.end()) { it == b.begin(); } // ^^ Wrong!
Mais je ne pense pas que ce soit une bonne idée: nous sommes sûrs que a.end ()! = B.end ()
?
Votre code en dépend.
Utilisation de range-v3 , votre référence pour tout ce qui concerne les plages en C + +17 ou version antérieure:
for (int i : view::concat(a, b)) { std::cout << i << ' '; }
Vous pouvez utiliser boost :: range :: join comme ceci:
#include <boost/range/join.hpp> ... std::vector<int> a{ 1,2,3 }; std::vector<int> b{ 4,5,6 }; for (auto i : boost::range::join(a, b)) { ... }
Pour obtenir le code le plus optimal, il est préférable d'éviter de faire des tests inutiles à chaque boucle. Puisque le code le plus efficace consiste à effectuer deux boucles, il est possible de s'en approcher en changeant l'état entier des variables qui participent à la boucle (itérateur et sentinelle), (je suppose que c'est ainsi que l'on implémente une plage concaténée. .. c'est comme ça que j'ai fait):
vector<int> a{ 1,2,3 }; vector<int> b{ 4,5,6 }; auto it = a.begin(); auto end = a.end(); for (;[&](){ if (it==end){ if (end==a.end()) { it=b.begin(); end=b.end(); return true; } else return false; } return true; }(); ++it) { //loop content }
Gamme Boost et Les algorithmes de bibliothèque standard sont des solutions qui devraient être préférées en raison de leur meilleure conception.
Cependant, par souci d'exhaustivité, si vous voulez vraiment appliquer l'idée derrière votre conception, vous pouvez coder comme suit:
std::vector<int> v1 = {1, 2, 3}; std::vector<int> v2 = {4, 5, 6}; for (auto it = v1.begin(); it != v2.end();) { if (it == v1.end()) { it = v2.begin(); } else { // { // use of *it // } ++it; } }
p>
Avec Visual Studio 2017, j'obtiens l'erreur "vector iterators incompatible". Je suppose que c'est parce que nous le comparons à v2.end () quand v1 c'est un itérateur v1.
Si la v1 et la v2 sont vides, vous aurez besoin de quelques vérifications supplémentaires.
@AndyG non. Le code vérifie tous les cas. Je ne pense pas qu'il y ait d'UB ici
@BiagioFesta: Vous avez raison, excuses. J'ai mal lu l ' utilisation de * it
comme en dehors du else
une autre façon de le faire en utilisant la plage de boost
#include <vector> #include <iostream> #include <boost/range.hpp> #include <boost/range/join.hpp> int main() { std::vector<int> a{ 1,2,3 }; std::vector<int> b{ 4,5,6 }; for(auto& x : boost::join(a, b)) { std::cout << x << " "; } std::cout << std::endl; }
Nous avons trouvé une manière «traditionnelle» simple de procéder.
for (int i = 0; i < 2; i++) { auto it = (i == 0) ? a.begin() : b.begin(); auto end = (i == 0) ? a.end() : b.end(); for (; it != end; ++it) { // do something with *it } }
Mais cela utilise deux boucles for?
@AndyG Désolé si j'ai confondu tout le monde, je voulais dire «ne pas utiliser deux boucles, une suivie d'une autre». Je ne voulais pas que le code "// fasse quelque chose avec * it" répété
Si vous avez envie d'écrire le vôtre, voici les conseils suivants:
template<class ForwardItr> struct range { ForwardItr beg; ForwardItr end; }; template<class ForwardItr, class F> void concat_ranges(range<ForwardItr> r1, range<ForwardItr> r2, F f) { auto run = [&f](range<ForwardItr> r) { for(auto itr = r.beg; itr != r.end; ++itr){ f(*itr); } }; run(r1); run(r2); };
Exemple: https://gcc.godbolt.org/z/8tPArY
Pas même une seule boucle for ()
ne nécessite d'imprimer ces conteneurs, si vous utilisez std :: copy
comme suit,
print( a.begin(), a.end(), std::string( " ")); print( b.begin(), b.end(), std::string( " "));
sortie: 1 2 3 4 5 6
Utiliser la bibliothèque stl est la meilleure option et il n'est pas nécessaire d'écrire de code pour imprimer un conteneur.
p >
Selon votre préoccupation Je ne veux pas écrire deux boucles for et dupliquer le code à l'intérieur de la boucle. Existe-t-il un moyen d'itérer sur a suivi de b avec une seule boucle for?
Le moyen d'éviter le code en double est d'écrire des fonctions qui peuvent être utilisées à plusieurs endroits, par exemple si vous ne ne veux pas utiliser std :: copy
et veut écrire votre propre code pour imprimer ces conteneurs (ce qui n'est pas recommandé) alors vous pouvez écrire la fonction suivante,
template< typename ForwardIterator> void print( ForwardIterator begin, ForwardIterator end, const std::string& separator) { while( begin != end) { std::cout<< *begin<< separator; ++begin; } }
Que diriez-vous d'écrire deux boucles (ou d'utiliser un algorithme standard, comme
std :: for_each
) et d'implémenter le comportement dupliqué dans une seule fonction qui sera transmise audit algorithme?@Fureeish Oui, c'est une possibilité
Quelqu'un voudrait expliquer pourquoi la question a été rejetée? J'ai fait mes recherches, et ce n'est pas une question de devoir.
Ensuite, j'irais avec cette approche. Forcer la logique à être enfermée dans une seule boucle introduira de nombreuses vérifications inutiles et un bruit de code difficile à lire. Préférez la simplicité à la complexité, surtout si le comportement et est soit le même, soit meilleur par rapport à l’approche la plus simple.