Itérer le vecteur C ++ de la fin au début


96

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

2
Dans C ++ 11, vous pouvez utiliser une boucle for basée sur la plage avec un adaptateur inverse, voir ici
MM

1
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 débordera (vector :: size () n'est pas signé), mais il est actuellement probable que vous n'atteindrez jamais cette limite (aussi la limite de vecteur actuelle sur les machines 32 bits est de 1 073 741 823).
Stefan Rogin

Réponses:


157

Le meilleur moyen est:

for (vector<my_class>::reverse_iterator i = my_vector.rbegin(); 
        i != my_vector.rend(); ++i ) { 
} 

rbegin()/ rend()ont été spécialement conçus à cet effet. (Et oui, incrémenter a reverse_interatorle fait reculer.)

Maintenant, en théorie, votre méthode (en utilisant begin()/ end()& --i) fonctionnerait, std::vectorl'itérateur étant bidirectionnel, mais rappelez-vous, ce end()n'est pas le dernier élément - c'est un au-delà du dernier élément, donc vous devez d'abord décrémenter, et vous êtes fait lorsque vous atteignez begin()- mais vous devez encore effectuer votre traitement.

vector<my_class>::iterator i = my_vector.end();
while (i != my_vector.begin())
{
     --i;
    /*do stuff */

} 

MISE À JOUR: J'étais apparemment trop agressif en réécrivant la for()boucle en while()boucle. (La partie importante est que le --iest au début.)


Je viens de réaliser que cela --iposera un gros problème si le conteneur est vide ... Avant de passer en do - whileboucle, il est logique de vérifier (my_vector.begin() != my_vector.end()).
a1ex07

1
Pourquoi utilisez-vous une do-whileboucle au lieu d'une simple whileboucle? Vous n'aurez alors pas besoin de contrôle spécial pour les vecteurs vides.
jamesdlin

Pourriez-vous mettre à jour la réponse à utiliser autopour une meilleure lisibilité?
LNJ

59

Si vous avez C ++ 11, vous pouvez utiliser auto.

for (auto it = my_vector.rbegin(); it != my_vector.rend(); ++it)
{
}

29

Le "modèle" bien établi pour l'itération inverse dans des plages fermées-ouvertes se présente comme suit

// Iterate over [begin, end) range in reverse
for (iterator = end; iterator-- != begin; ) {
  // Process `*iterator`
}

ou, si vous préférez,

// Iterate over [begin, end) range in reverse
for (iterator = end; iterator != begin; ) {
  --iterator;
  // Process `*iterator`
}

Ce modèle est utile, par exemple, pour l'indexation inverse d'un tableau à l'aide d'un index non signé

int array[N];
...
// Iterate over [0, N) range in reverse
for (unsigned i = N; i-- != 0; ) {
  array[i]; // <- process it
}

(Les personnes qui ne sont pas familières avec ce modèle insistent souvent sur l'utilisation de types entiers signés pour l'indexation de tableaux, car elles pensent à tort que les types non signés sont en quelque sorte "inutilisables" pour l'indexation inversée)

Il peut être utilisé pour itérer sur un tableau en utilisant une technique de "pointeur glissant"

// Iterate over [array, array + N) range in reverse
for (int *p = array + N; p-- != array; ) {
  *p; // <- process it
}

ou il peut être utilisé pour une itération inverse sur un vecteur en utilisant un itérateur ordinaire (et non inversé)

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

cppreference.com dit que l'accès à l'élément à end () "entraîne un comportement indéfini", donc je pense que les boucles devraient commencer à--end()
Thomas Schmid

@ThomasSchmid Ces boucles ne tentent jamais d'accéder à end(). Même s'ils semblent commencer à end(), ils s'assurent toujours de décrémenter l'itérateur avant le premier accès.
AnT

C'est tellement plus agréable que rbegin / rend car vous pouvez faire une boucle dans l'autre sens au moment de l'exécution (pas de modèle) auto a = vector<int>{0,1,2}; bool reversed = 0; auto it = (!reversed?a.begin():a.end()); auto end = (reversed?a.begin():a.end()); while(it != end) { if(reversed)--it; cout << *it << endl; if(!reversed)++it; }
colin

@colin Egads! ce moche !. Vous testez reversed quatre fois - deux d'entre eux dans une boucle. Bien sûr, tester un booléen est très rapide, mais quand même, pourquoi travailler n'est-il pas nécessaire? Surtout, puisque le seul but semble être de rendre le code illisible. comment utiliser deux boucles séparées? if (reversed) for (auto it = my_vector.rbegin(); it != my_vector.rend(); ++it) {doStuff(*it);} else for (auto it = my_vector.begin(); it != my_vector.end(); ++it) {doStuff(*it);}
James Curran

En fait, vous avez manqué mon point. Vous avez tout à fait raison de le diviser en deux ifmais je voulais me débarrasser du modèle sur le doStuff(). C'est toujours faisable avec les deux que ifvous avez en faisant une boucle dans l'autre sens sur le premier.
colin

11

À partir de c ++ 20, vous pouvez utiliser une std::ranges::reverse_viewet une boucle for basée sur une plage:

#include<ranges>
#include<vector>
#include<iostream>

using namespace std::ranges;

std::vector<int> const vec{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

for(auto& i :  views::reverse(vec)) {
    std::cout << i << ",";
}

Ou même

for(auto& i :  vec | views::reverse)

Malheureusement, au moment de la rédaction de cet article (janvier 2020), aucun compilateur majeur n'implémente la bibliothèque de gammes, mais vous pouvez recourir aux gammes-v3 d' Eric Niebler :

#include <iostream>
#include <vector>
#include "range/v3/all.hpp"

int main() {

    using namespace ranges;

    std::vector<int> const vec{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    for(auto& i :  views::reverse(vec)) {
        std::cout << i << ",";
    }

    return 0;
}

9

rend() / rbegin()Itérateurs utilisateur :

for (vector<myclass>::reverse_iterator it = myvector.rbegin(); it != myvector.rend(); it++)


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

Sinon, en C ++ 14, faites simplement:

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

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

Enfin, vous pouvez écrire l'adaptateur de plage backwardscomme 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 vous pouvez le faire:

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

ce que je trouve assez joli.



1

Voici une implémentation super simple qui permet d'utiliser le pour chaque construction et ne repose que sur la bibliothèque std 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 des choses qui fournissent un rbegin () et un rend (), ainsi qu'avec des 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))
    ;

1

Si vous pouvez utiliser la bibliothèque Boost, il existe la gamme Boost.Range qui fournit l' reverseadaptateur de plage en incluant:

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

Ensuite, en combinaison avec une boucle de plage de C ++ 11for , vous pouvez simplement écrire ce qui suit:

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

Étant donné que ce code est plus bref que celui utilisant la paire d'itérateurs, il peut être plus lisible et moins sujet aux erreurs car il y a moins de détails à prêter attention.


1
En effet, boost::adaptors::reversec'est très utile!
Kai Petzke

0

J'aime l'itérateur à l'envers à la fin de Yakk - la réponse d'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 celui-ci:

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

et changez-le en ceci pour itérer en sens inverse:

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

-1

utiliser ce code

//print the vector element in reverse order by normal iterator.
cout <<"print the vector element in reverse order by normal iterator." <<endl;
vector<string>::iterator iter=vec.end();
--iter;
while (iter != vec.begin())
{
    cout << *iter  << " "; 
    --iter;
}

1
Ce code échoue terriblement, s'il vecfait référence à un vecteur vide!
Kai Petzke

-2

Comme je ne veux pas introduire de nouvelle syntaxe C ++ de type extraterrestre, et que je veux simplement construire sur des primitives existantes, les extraits ci-dessous semblent fonctionner:

#include <vector>
#include <iostream>

int main (int argc,char *argv[])
{
    std::vector<int> arr{1,2,3,4,5};
    std::vector<int>::iterator it;

    // iterate forward
    for (it = arr.begin(); it != arr.end(); it++) {
        std::cout << *it << " ";
    }

    std::cout << "\n************\n";
 
    if (arr.size() > 0) {
        // iterate backward, simple Joe version
        it = arr.end() - 1;
        while (it != arr.begin()) {
            std::cout << *it << " ";
            it--;
        }
        std::cout << *it << " ";
    } 

    // iterate backwards, the C++ way
    std::vector<int>::reverse_iterator rit;
    for (rit = arr.rbegin(); rit != arr.rend(); rit++) {
        std::cout << *rit << " ";
    }

    return 0;
}

Ce code échoue terriblement, s'il arrfait référence à un vecteur vide!
Kai Petzke
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.