3
votes

Existe-t-il un moyen d'itérer sur deux conteneurs sans utiliser deux boucles for

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.


4 commentaires

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.


9 Réponses :


0
votes

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.


0 commentaires

8
votes

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


0 commentaires

1
votes

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))
{
    ...
}


0 commentaires

-1
votes

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
}


0 commentaires

2
votes

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

Démo en direct ici

p>


4 commentaires

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



2
votes

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


0 commentaires

1
votes

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


2 commentaires

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é



1
votes

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


0 commentaires

0
votes

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


0 commentaires