132 votes

Itération du vecteur C++ de la fin au début

Est-il possible d'itérer un vecteur de la fin au début ?

for (vector<my_class>::iterator i = my_vector.end();
        i != my_vector.begin(); /* ?! */ ) {
}

Ou est-ce seulement possible avec quelque chose comme ça :

for (int i = my_vector.size() - 1; i >= 0; --i) {
}

3 votes

En C++11, vous pouvez utiliser un for-loop basé sur l'intervalle avec un adaptateur inverse, voir ici

1 votes

Théoriquement, sur une machine 32 bits, pour la deuxième solution, si la taille du vecteur est supérieure à 2 147 483 647 + 1, il y aura un débordement (vector::size() est non signé), mais actuellement, il y a de fortes chances que vous n'atteigniez jamais cette limite (la limite actuelle des vecteurs sur les machines 32 bits est également de 1 073 741 823).

2 votes

Le problème de débordement de @StefanRogin devient réel lorsqu'au lieu de "int i" dans la boucle for, quelqu'un utilise size_t (ou peut-être auto) dans sa quête pour éviter les avertissements du compilateur (en raison de l'affectation de size() à int). Avec cela, et pour un vecteur à un seul élément, la deuxième itération déborde auto i et la boucle s'exécute avec le "i" débordé, ce qui entraîne toutes sortes de plantages.

7voto

Steve Townsend Points 36948

Utiliser des itérateurs inversés et des boucles de rbegin() a rend()

7voto

Yakk Points 31636
template<class It>
std::reverse_iterator<It> reversed( It it ) {
  return std::reverse_iterator<It>(std::forward<It>(it));
}

Ensuite :

for( auto rit = reversed(data.end()); rit != reversed(data.begin()); ++rit ) {
  std::cout << *rit;

Alternativement, en C++14, il suffit de faire :

for( auto rit = std::rbegin(data); rit != std::rend(data); ++rit ) {
  std::cout << *rit;

En C++03/11, la plupart des conteneurs standard ont une fonction .rbegin() y .rend() également.

Enfin, vous pouvez écrire l'adaptateur de gamme backwards comme suit :

namespace adl_aux {
  using std::begin; using std::end;
  template<class C>
  decltype( begin( std::declval<C>() ) ) adl_begin( C&& c ) {
    return begin(std::forward<C>(c));
  }
  template<class C>
  decltype( end( std::declval<C>() ) ) adl_end( C&& c ) {
    return end(std::forward<C>(c));
  }
}

template<class It>
struct simple_range {
  It b_, e_;
  simple_range():b_(),e_(){}
  It begin() const { return b_; }
  It end() const { return e_; }
  simple_range( It b, It e ):b_(b), e_(e) {}

  template<class OtherRange>
  simple_range( OtherRange&& o ):
    simple_range(adl_aux::adl_begin(o), adl_aux::adl_end(o))
  {}

  // explicit defaults:
  simple_range( simple_range const& o ) = default;
  simple_range( simple_range && o ) = default;
  simple_range& operator=( simple_range const& o ) = default;
  simple_range& operator=( simple_range && o ) = default;
};
template<class C>
simple_range< decltype( reversed( adl_aux::adl_begin( std::declval<C&>() ) ) ) >
backwards( C&& c ) {
  return { reversed( adl_aux::adl_end(c) ), reversed( adl_aux::adl_begin(c) ) };
}

et maintenant tu peux faire ça :

for (auto&& x : backwards(ctnr))
  std::cout << x;

ce que je trouve assez joli.

2voto

John Stephen Points 2739

J'aime bien l'itérateur inversé à la fin de la réponse de Yakk - Adam Nevraumont, mais cela me semblait compliqué pour ce dont j'avais besoin, alors j'ai écrit ceci :

template <class T>
class backwards {
    T& _obj;
public:
    backwards(T &obj) : _obj(obj) {}
    auto begin() {return _obj.rbegin();}
    auto end() {return _obj.rend();}
};

Je suis capable de prendre un itérateur normal comme ça :

for (auto &elem : vec) {
    // ... my useful code
}

et le changer en ceci pour itérer en sens inverse :

for (auto &elem : backwards(vec)) {
    // ... my useful code
}

2voto

El Profesor Points 10915

Si vous pouvez utiliser la bibliothèque Boost, il y a le Gamme Boost. qui fournit le reverse adaptateur de gamme en incluant :

#include <boost/range/adaptor/reversed.hpp>

Ensuite, en combinaison avec un La gamme de C++11- for boucle vous pouvez simplement écrire ce qui suit :

for (auto& elem: boost::adaptors::reverse(my_vector)) {
   // ...
}

Comme ce code est plus court que celui qui utilise la paire d'itérateurs, il peut être plus lisible et moins sujet aux erreurs car il y a moins de détails auxquels il faut faire attention.

1voto

Mordachai Points 3234

Voici une implémentation très simple qui permet d'utiliser la construction for each et qui repose uniquement sur la bibliothèque std du C++14 :

namespace Details {

    // simple storage of a begin and end iterator
    template<class T>
    struct iterator_range
    {
        T beginning, ending;
        iterator_range(T beginning, T ending) : beginning(beginning), ending(ending) {}

        T begin() const { return beginning; }
        T end() const { return ending; }
    };

}

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// usage:
//  for (auto e : backwards(collection))
template<class T>
auto backwards(T & collection)
{
    using namespace std;
    return Details::iterator_range(rbegin(collection), rend(collection));
}

Cela fonctionne avec les choses qui fournissent un rbegin() et rend(), ainsi qu'avec les tableaux statiques.

std::vector<int> collection{ 5, 9, 15, 22 };
for (auto e : backwards(collection))
    ;

long values[] = { 3, 6, 9, 12 };
for (auto e : backwards(values))
    ;

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X