Iterazione del vettore C ++ dalla fine all'inizio


96

È possibile iterare un vettore dalla fine all'inizio?

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

O è possibile solo con qualcosa del genere:

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

2
In C ++ 11 puoi usare for-loop basato su intervallo con adattatore inverso, vedi qui
MM

1
teoricamente, su una macchina a 32 bit, per la seconda soluzione, se la dimensione del vettore è maggiore di 2.147.483.647 + 1 andrà in overflow (vector :: size () è senza segno), ma attualmente è probabile che non raggiungerai mai quel limite (anche il limite di vettore corrente su macchine a 32 bit è 1.073.741.823).
Stefan Rogin

Risposte:


157

Il modo migliore è:

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

rbegin()/ rend()sono stati progettati appositamente per quello scopo. (E sì, l'incremento di a lo reverse_interatorsposta indietro.)

Ora, in teoria, il tuo metodo (usando begin()/ end()& --i) funzionerebbe, std::vectorl'iteratore è bidirezionale, ma ricorda, end()non è l'ultimo elemento - è uno oltre l'ultimo elemento, quindi dovresti prima decrementare e tu sei fatto quando raggiungi begin()- ma devi ancora fare la tua elaborazione.

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

} 

AGGIORNAMENTO: Apparentemente ero troppo aggressivo nel riscrivere il for()loop in un while()loop. (La parte importante è che --iè all'inizio.)


Mi sono appena reso conto che --icauserà un grosso problema se il contenitore è vuoto ... Prima di andare in do - whileloop ha senso controllare (my_vector.begin() != my_vector.end()).
a1ex07

1
Perché stai usando un do-whileloop invece di un semplice whileloop? Quindi non avrai bisogno di alcun controllo speciale per i vettori vuoti.
jamesdlin

Potresti aggiornare la risposta da utilizzare autoper una migliore leggibilità?
LNJ

59

Se hai C ++ 11 puoi usare auto.

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

29

Il "modello" ben consolidato per l'iterazione inversa attraverso intervalli chiusi-aperti appare come segue

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

oppure, se preferisci,

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

Questo modello è utile, ad esempio, per l'indicizzazione inversa di un array utilizzando un indice senza segno

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

(Le persone non hanno familiarità con questo schema spesso insistono sull'uso firmato integer tipi per serie indicizzazione specificamente perché credono erroneamente che i tipi non firmati sono in qualche modo "inutilizzabili" per l'indicizzazione inversa)

Può essere utilizzato per iterare su un array utilizzando una tecnica di "puntatore scorrevole"

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

oppure può essere utilizzato per l'iterazione inversa su un vettore utilizzando un iteratore ordinario (non inverso)

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

cppreference.com dice, l'accesso all'elemento alla fine () "si traduce in un comportamento indefinito", quindi penso che i cicli dovrebbero iniziare a--end()
Thomas Schmid

@ThomasSchmid Questi loop non tentano mai di accedere a end(). Anche se sembrano iniziare da end(), si assicurano sempre di diminuire l'iteratore prima del primo accesso.
AnT

Questo è molto più bello di rbegin / rend perché puoi eseguire il loop nell'altro modo in fase di esecuzione (senza modello) 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! che brutto !. Stai testando reversed quattro volte, due delle quali all'interno di un ciclo. Ovviamente, testare un booleano è molto veloce, ma comunque, perché per lavorare non è necessario? Soprattutto, poiché l'unico scopo sembra essere quello di rendere illeggibile il codice. che ne dici di usare due loop separati? 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

In realtà ti sei perso il mio punto. Hai assolutamente ragione a dividerlo in due if, ma volevo eliminare il modello sul file doStuff(). Comunque fattibile con le due ifs che hai girando al contrario sul primo.
colin

11

A partire da c ++ 20, puoi usare a std::ranges::reverse_viewe un ciclo for basato su intervallo:

#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 << ",";
}

O anche

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

Sfortunatamente, al momento della stesura di questo articolo (gennaio 2020) nessun compilatore principale implementa la libreria di intervalli, ma puoi ricorrere alla gamma di Eric Niebler-v3 :

#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()Iteratori utente :

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

Poi:

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

In alternativa in C ++ 14 basta fare:

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

In C ++ 03/11 la maggior parte dei contenitori standard ha anche un metodo .rbegin()and .rend().

Infine, puoi scrivere l'adattatore di intervallo backwardscome segue:

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

e ora puoi farlo:

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

che penso sia abbastanza carino.



1

Ecco un'implementazione semplicissima che consente l'uso del costrutto for each e si basa solo sulla libreria 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));
}

Funziona con cose che forniscono un rbegin () e rend (), così come con array statici.

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

Se puoi usare The Boost Library, c'è Boost.Range che fornisce l' reverseadattatore di portata includendo:

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

Quindi, in combinazione con un ciclo di intervallo di C ++ 11for , puoi semplicemente scrivere quanto segue:

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

Poiché questo codice è più breve di quello che utilizza la coppia di iteratori, potrebbe essere più leggibile e meno soggetto a errori poiché ci sono meno dettagli a cui prestare attenzione.


1
In effetti, boost::adaptors::reverseè molto utile!
Kai Petzke

0

Mi piace l'iteratore all'indietro alla fine di Yakk - la risposta di Adam Nevraumont, ma sembrava complicato per quello di cui avevo bisogno, quindi ho scritto questo:

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

Sono in grado di prendere un normale iteratore come questo:

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

e modificalo in questo per iterare al contrario:

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

-1

usa questo codice

//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
Questo codice fallisce terribilmente, se si vecriferisce a un vettore vuoto!
Kai Petzke

-2

Poiché non voglio introdurre una nuova sintassi C ++ di tipo alieno e voglio semplicemente costruire su primitive esistenti, i frammenti di seguito sembrano funzionare:

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

Questo codice fallisce terribilmente, se si arrriferisce a un vettore vuoto!
Kai Petzke
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.