Iterasi vektor C ++ dari akhir ke awal

101

Apakah mungkin untuk mengulang sebuah vektor dari akhir ke awal?

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

Atau itu hanya mungkin dengan hal seperti itu:

for (int i = my_vector.size() - 1; i >= 0; --i) {
}
pengguna
sumber
2
Di C ++ 11 Anda dapat menggunakan for-loop berbasis jangkauan dengan adaptor terbalik, lihat di sini
MM
1
secara teoritis, pada mesin 32 bit, untuk solusi kedua, jika ukuran vektor lebih besar dari 2.147.483.647 + 1 itu akan meluap (vector :: size () unsigned), tetapi saat ini kemungkinan besar Anda tidak akan pernah mencapai batas itu (juga batas vektor saat ini pada mesin 32 bit adalah 1.073.741.823).
Stefan Rogin
@StefanRogin overflow masalah menjadi nyata ketika bukannya "int i" di loop for seseorang menggunakan size_t (atau mungkin otomatis) dalam pencarian mereka untuk menghindari peringatan compiler (karena size () tugas ke int). Dengan ini, dan untuk vektor elemen tunggal, iterasi kedua meluap otomatis i dan loop dijalankan dengan "i" yang meluap sehingga mengakibatkan semua jenis error.
n-mam

Jawaban:

163

Cara terbaik adalah:

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

rbegin()Aku rend()dirancang khusus untuk tujuan itu. (Dan ya, menambahkan reverse_interatorgerakan ke belakang.)

Sekarang, secara teori, metode Anda (menggunakan begin()/ end()& --i) akan berfungsi, std::vectoriterator menjadi dua arah, tetapi ingat, end()bukan elemen terakhir - ini adalah salah satu di luar elemen terakhir, jadi Anda harus mengurangi terlebih dahulu, dan Anda selesai saat Anda mencapai begin()- tetapi Anda masih harus melakukan pemrosesan.

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

} 

UPDATE: Saya tampaknya terlalu agresif dalam menulis ulang for()loop menjadi satu while()lingkaran. (Bagian yang penting adalah bahwa --iada di awal.)

James Curran
sumber
Saya baru menyadari itu --iakan menyebabkan masalah besar jika wadah kosong ... Sebelum masuk ke do - whileloop masuk akal untuk memeriksa (my_vector.begin() != my_vector.end()).
a1ex07
1
Mengapa Anda menggunakan do-whileloop, bukan hanya satu whileloop? Maka Anda tidak memerlukan pemeriksaan khusus untuk vektor kosong.
jamesdlin
Bisakah Anda memperbarui jawaban untuk digunakan autoagar lebih mudah dibaca?
LNJ
60

Jika Anda memiliki C ++ 11, Anda dapat memanfaatkan auto.

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

"Pola" yang mapan untuk perulangan balik melalui rentang buka-tertutup terlihat sebagai berikut

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

atau, jika Anda lebih suka,

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

Pola ini berguna, misalnya, untuk mengindeks terbalik sebuah array menggunakan indeks yang tidak ditandatangani

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

(Orang yang tidak terbiasa dengan pola ini sering kali bersikeras menggunakan jenis bilangan bulat bertanda tangan untuk pengindeksan larik secara khusus karena mereka salah percaya bahwa jenis tak bertanda tangan entah bagaimana "tidak dapat digunakan" untuk pengindeksan terbalik)

Ini dapat digunakan untuk iterasi di atas array menggunakan teknik "pointer geser"

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

atau dapat digunakan untuk iterasi balik pada vektor menggunakan iterator biasa (bukan terbalik)

for (vector<my_class>::iterator i = my_vector.end(); i-- != my_vector.begin(); ) {
  *i; // <- process it
}
Semut
sumber
cppreference.com mengatakan, mengakses elemen di end () "menghasilkan perilaku tidak terdefinisi", jadi menurut saya, loop harus dimulai pada--end()
Thomas Schmid
1
@ThomasSchmid Perulangan ini tidak pernah mencoba mengakses di end(). Meskipun tampaknya mulai end(), mereka selalu memastikan untuk mengurangi iterator sebelum akses pertama.
AnT
Ini jauh lebih baik daripada rbegin / rend karena Anda dapat melakukan loop ke arah lain saat runtime (tanpa template) 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
@ Egads @olin! itu jelek !. Anda menguji reversed empat kali - dua di antaranya di dalam satu loop. Tentu saja, menguji boolean sangat cepat, tetapi tetap saja, mengapa Anda tidak perlu bekerja? Terutama, karena satu-satunya tujuan tampaknya membuat kode tidak dapat dibaca. bagaimana kalau kita menggunakan dua loop terpisah? 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
Sebenarnya Anda melewatkan poin saya. Anda benar untuk membaginya menjadi dua if, tetapi saya ingin menyingkirkan template di file doStuff(). Namun tetap dapat dilakukan dengan dua ifyang Anda miliki dengan mengulang sebaliknya pada yang pertama.
colin
17

Dimulai dengan c ++ 20, Anda dapat menggunakan a std::ranges::reverse_viewdan for-loop berbasis rentang:

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

Atau bahkan

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

Sayangnya, pada saat penulisan (Jan 2020) tidak ada kompilator utama yang mengimplementasikan pustaka rentang, tetapi Anda dapat menggunakan rentang-v3 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;
}
florestan
sumber
9

rend() / rbegin()Iterator pengguna :

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

a1ex07
sumber
6
template<class It>
std::reverse_iterator<It> reversed( It it ) {
  return std::reverse_iterator<It>(std::forward<It>(it));
}

Kemudian:

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

Atau di C ++ 14 lakukan saja:

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

Dalam C ++ 11/03 paling kontainer standar memiliki .rbegin()dan .rend()metode juga.

Terakhir, Anda dapat menulis adaptor rentang backwardssebagai berikut:

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

dan sekarang Anda dapat melakukan ini:

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

yang menurut saya cukup cantik.

Yakk - Adam Nevraumont
sumber
5

Gunakan iterator terbalik dan putar dari rbegin()kerend()

Steve Townsend
sumber
1

Saya suka iterator mundur di akhir Yakk - jawaban Adam Nevraumont, tetapi tampaknya rumit untuk apa yang saya butuhkan, jadi saya menulis ini:

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

Saya dapat menggunakan iterator normal seperti ini:

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

dan mengubahnya menjadi ini untuk mengulang secara terbalik:

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

Berikut adalah implementasi super sederhana yang memungkinkan penggunaan untuk setiap konstruksi dan hanya bergantung pada pustaka C ++ 14 std:

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

Ini bekerja dengan hal-hal yang menyediakan rbegin () dan rend (), serta dengan array statis.

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

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

Jika Anda dapat menggunakan The Boost Library, ada Boost.Range yang menyediakan reverseadaptor rentang dengan memasukkan:

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

Kemudian, dalam kombinasi dengan range- forloop C ++ 11 , Anda cukup menulis yang berikut ini:

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

Karena kode ini lebih singkat daripada yang menggunakan pasangan iterator, mungkin lebih mudah dibaca dan tidak terlalu rentan terhadap kesalahan karena lebih sedikit detail yang harus diperhatikan.

眠 り ネ ロ ク
sumber
1
Memang boost::adaptors::reversesangat bermanfaat!
Kai Petzke
-1

gunakan kode ini

//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
sumber
1
Kode ini gagal total, jika vecmengacu pada vektor kosong!
Kai Petzke
-2

Karena saya tidak ingin memperkenalkan sintaks C ++ baru yang mirip alien, dan saya hanya ingin membangun primitif yang sudah ada, cuplikan di bawah ini tampaknya berfungsi:

#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;
}
eigenfield.dll
sumber
Kode ini gagal total, jika arrmengacu pada vektor kosong!
Kai Petzke