Iterowanie wektora C ++ od końca do początku

101

Czy można iterować wektor od końca do początku?

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

Czy jest to możliwe tylko w przypadku czegoś takiego:

for (int i = my_vector.size() - 1; i >= 0; --i) {
}
użytkownik
źródło
2
W C ++ 11 możesz użyć pętli for opartej na zakresie z adapterem odwrotnym, zobacz tutaj
MM
1
teoretycznie na 32-bitowej maszynie, dla drugiego rozwiązania, jeśli rozmiar wektora jest większy niż 2147 483 647 + 1, nastąpi przepełnienie (vector :: size () jest bez znaku), ale obecnie są szanse, że nigdy nie przekroczysz tego limitu (również obecny limit wektora na maszynach 32-bitowych wynosi 1 073 741 823).
Stefan Rogin
Problem przepełnienia @StefanRogin staje się realny, gdy zamiast "int i" w pętli for ktoś używa size_t (lub może auto) w celu uniknięcia ostrzeżeń kompilatora (z powodu przypisania size () do int). W tym przypadku i dla pojedynczego wektora elementu druga iteracja przepełnia auto i, a pętla jest wykonywana z przepełnionym „i”, co powoduje różnego rodzaju awarie.
n-mam

Odpowiedzi:

164

Najlepszy sposób to:

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

rbegin()/ rend()zostały specjalnie zaprojektowane do tego celu. (I tak, zwiększenie wartości a reverse_interatorprzesuwa ją wstecz).

Teoretycznie twoja metoda (przy użyciu begin()/ end()& --i) zadziała, std::vectorponieważ iterator jest dwukierunkowy, ale pamiętaj, że end()nie jest ostatnim elementem - jest poza ostatnim elementem, więc musisz najpierw zmniejszyć, a jesteś gotowe, gdy dotrzesz begin()- ale nadal musisz wykonać przetwarzanie.

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

} 

AKTUALIZACJA: Najwyraźniej zbyt agresywnie przepisałem for()pętlę w while()pętlę. (Ważne jest to, że --ijest na początku).

James Curran
źródło
Właśnie zdałem sobie sprawę, że --ispowoduje to duży problem, jeśli pojemnik jest pusty ... Przed przejściem do do - whilepętli warto sprawdzić (my_vector.begin() != my_vector.end()).
a1ex07
1
Dlaczego używasz do-whilepętli zamiast tylko whilepętli? Wtedy nie potrzebowałbyś żadnego specjalnego sprawdzenia pustych wektorów.
jamesdlin
Czy możesz zaktualizować odpowiedź, aby użyć jej autodla lepszej czytelności?
LNJ
60

Jeśli masz C ++ 11, możesz skorzystać z auto.

for (auto it = my_vector.rbegin(); it != my_vector.rend(); ++it)
{
}
Akavall
źródło
30

Ugruntowany „wzorzec” odwrotnej iteracji poprzez zakresy zamknięte-otwarte wygląda następująco

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

lub, jeśli wolisz,

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

Ten wzorzec jest przydatny na przykład do odwrotnego indeksowania tablicy przy użyciu indeksu bez znaku

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

(Ludzie, którzy nie znają tego wzorca często nalegać na użyciu podpisane całkowitą typów dla indeksowania tablicy specjalnie ponieważ błędnie uważają, że niepodpisane typy są jakoś „bezużyteczny” na odwrotnej indeksowania)

Może być używany do iteracji po tablicy przy użyciu techniki „przesuwnego wskaźnika”

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

lub może być używany do odwrotnej iteracji po wektorze przy użyciu zwykłego (nie odwrotnego) iteratora

for (vector<my_class>::iterator i = my_vector.end(); i-- != my_vector.begin(); ) {
  *i; // <- process it
}
Mrówka
źródło
cppreference.com mówi, że dostęp do elementu w end () „skutkuje niezdefiniowanym zachowaniem”, więc myślę, że pętle powinny zaczynać się od--end()
Thomas Schmid,
1
@ThomasSchmid Te pętle nigdy nie próbują uzyskać dostępu do adresu end(). Mimo że wydają się zaczynać od end(), zawsze upewniają się, że iterator jest dekrementowany przed pierwszym dostępem.
AnT
To jest o wiele ładniejsze niż rbegin / rend, ponieważ możesz zapętlić w drugą stronę w czasie wykonywania (bez szablonu) 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
1
@colin Egads! ten brzydki !. Testujesz reversed cztery razy - dwa z nich w pętli. Oczywiście testowanie wartości logicznych jest bardzo szybkie, ale nadal, dlaczego nie musisz pracować? Zwłaszcza, że ​​jedynym celem wydaje się uczynienie kodu nieczytelnym. co powiesz na to, że użyjemy dwóch oddzielnych pętli? 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,
Właściwie przegapiłeś mój punkt widzenia. Masz absolutną rację, dzieląc to na dwie części, ifale chciałem pozbyć się szablonu na doStuff(). Nadal jest to wykonalne z dwoma, ifktóre masz, wykonując pętlę w drugą stronę na pierwszym.
colin
17

Począwszy od C ++ 20, możesz użyć std::ranges::reverse_viewpętli for opartej na zakresach:

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

Lub nawet

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

Niestety, w chwili pisania tego tekstu (styczeń 2020 r.) Żaden główny kompilator nie implementuje biblioteki zakresów, ale możesz skorzystać z zakresów Erica Nieblera-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;
}
florestan
źródło
9

rend() / rbegin()Iteratory użytkownika :

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

a1ex07
źródło
6
template<class It>
std::reverse_iterator<It> reversed( It it ) {
  return std::reverse_iterator<It>(std::forward<It>(it));
}

Następnie:

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

Alternatywnie w C ++ 14 po prostu zrób:

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

W C ++ 03/11 większość standardowe kontenery mają .rbegin()i .rend()metody, jak również.

Na koniec możesz napisać adapter zakresu backwardsw następujący sposób:

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

a teraz możesz to zrobić:

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

co myślę, że jest całkiem ładne.

Yakk - Adam Nevraumont
źródło
5

Użyj odwrotnych iteratorów i pętli od rbegin()dorend()

Steve Townsend
źródło
1

Podoba mi się iterator wstecz na końcu Yakk - odpowiedź Adama Nevraumonta, ale wydawało mi się, że jest to skomplikowane dla tego, czego potrzebowałem, więc napisałem to:

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

Jestem w stanie wziąć normalny iterator, taki jak ten:

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

i zmień to na to, aby iterować w odwrotnej kolejności:

for (auto &elem : backwards(vec)) {
    // ... my useful code
}
John Stephen
źródło
1

Oto super prosta implementacja, która pozwala na użycie dla każdej konstrukcji i opiera się tylko na bibliotece standardowej 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));
}

Działa to z rzeczami, które dostarczają rbegin () i rend (), a także z tablicami statycznymi.

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

long values[] = { 3, 6, 9, 12 };
for (auto e : backwards(values))
    ;
Mordachaj
źródło
1

Jeśli możesz korzystać z The Boost Library, istnieje Boost.Range, który zapewnia reverseadapter zasięgu obejmujący:

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

Następnie, w połączeniu z pętlą zakresu C ++ 11for , możesz po prostu napisać:

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

Ponieważ ten kod jest krótszy niż ten wykorzystujący parę iteratorów, może być bardziej czytelny i mniej podatny na błędy, ponieważ jest mniej szczegółów, na które należy zwrócić uwagę.

眠 り ネ ロ ク
źródło
1
Rzeczywiście, boost::adaptors::reversejest bardzo przydatne!
Kai Petzke
-1

użyj tego kodu

//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;
}
amit kumar
źródło
1
Ten kod strasznie zawodzi, jeśli vecodnosi się do pustego wektora!
Kai Petzke
-2

Ponieważ nie chcę wprowadzać nowej składni C ++ podobnej do obcych, a po prostu chcę zbudować na istniejących prymitywach, poniższe fragmenty wydają się działać:

#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;
}
własne
źródło
Ten kod strasznie zawodzi, jeśli arrodnosi się do pustego wektora!
Kai Petzke