Ітерація вектора C ++ від кінця до початку


96

Чи можна повторити вектор з кінця до початку?

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

Або це можливо лише з подібним:

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

2
У C ++ 11 ви можете використовувати цикл for-loop на основі діапазону із зворотним адаптером, див. Тут
MM

1
теоретично, на 32-бітній машині, для другого рішення, якщо розмір вектора більший за 2 147 483 647 + 1, він переллється (vector :: size () не підписано), але наразі є ймовірність того, що ви ніколи не досягнете цієї межі (також поточний ліміт векторів на 32-розрядних машинах становить 1073 741 823).
Стефан Рогін

Відповіді:


157

Найкращий спосіб:

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

rbegin()/ rend()були спеціально розроблені для цієї мети. (І так, при збільшенні a reverse_interatorрухається назад).

Тепер теоретично ваш метод (з використанням begin()/ end()& --i) буде працювати, std::vectorітератор буде двонаправленим, але пам’ятайте, end()це не останній елемент - це один за останнім елементом, тому вам доведеться декрементувати спочатку, і ви робиться, коли ви досягаєте begin()- але вам все одно доведеться виконати обробку.

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

} 

ОНОВЛЕННЯ: Я, мабуть, був занадто агресивним, переписуючи for()цикл у while()цикл. (Важливим є те, що --iце на початку.)


Я щойно зрозумів, що --iце спричинить велику проблему, якщо контейнер порожній ... Перед входом у do - whileцикл має сенс перевірити (my_vector.begin() != my_vector.end()).
a1ex07

1
Чому ви використовуєте do-whileцикл замість просто whileциклу? Тоді вам не знадобиться спеціальна перевірка порожніх векторів.
jamesdlin

Не могли б ви оновити відповідь autoдля кращої читабельності?
LNJ

58

Якщо у вас C ++ 11, ви можете скористатися auto.

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

28

Добре встановлений "шаблон" для зворотної ітерації через закрито-відкриті діапазони виглядає наступним чином

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

або, якщо вам більше подобається,

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

Цей шаблон корисний, наприклад, для зворотної індексації масиву з використанням беззнакового індексу

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

(Люди, незнайомі з цим шаблоном, часто наполягають на використанні цілочисельних типів із підписом для індексації масивів, оскільки вони неправильно вважають, що типи без знаків якимось чином "непридатні" для зворотного індексування)

Його можна використовувати для ітерації масиву за допомогою техніки "ковзання вказівника"

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

або його можна використовувати для зворотної ітерації по вектору за допомогою звичайного (не зворотного) ітератора

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

cppreference.com каже, що доступ до елемента end () "призводить до невизначеної поведінки", тому, я думаю, цикли повинні починатися з--end()
Thomas Schmid

@ThomasSchmid Ці цикли ніколи не намагаються отримати доступ за адресою end(). Хоча вони, здається, починаються з end(), вони завжди переконуються зменшити ітератор перед першим доступом.
AnT

Це набагато приємніше, ніж rbegin / rend, тому що ви можете виконувати інший шлях під час виконання (без шаблону) 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! що негарно !. Ви тестуєте reversed чотири рази - два з них у циклі. Звичайно, тестування логічної будови відбувається дуже швидко, але все ж, чому працювати не потрібно? Особливо, оскільки, здається, єдиною метою є зробити код нечитабельним. як щодо того, що ми використовуємо дві окремі петлі? 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);}
Джеймс

Насправді ви пропустили мою думку. Ви абсолютно праві, розділивши його на дві ifs, але я хотів позбутися шаблону на doStuff(). Все ще здійсненно, хоча з двома, ifякі ви маєте, прокручуючи навпаки на першому.
colin

11

Починаючи з c ++ 20, ви можете використовувати std::ranges::reverse_viewцикл for і на основі діапазону:

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

Або навіть

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

На жаль, на момент написання статті (січень 2020 р.) Жоден основний компілятор не реалізує бібліотеку діапазонів, але ти можеш вдатися до діапазону Еріка Ніблер-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()Ітератори користувачів :

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

Тоді:

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

В якості альтернативи в C ++ 14 просто виконайте:

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

У C ++ 03/11 більшість стандартних контейнерів мають .rbegin()і .rend()метод , а також.

Нарешті, ви можете написати адаптер діапазону backwardsнаступним чином:

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

і тепер ви можете зробити це:

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

що, на мою думку, досить гарне.



1

Ось надзвичайно проста реалізація, яка дозволяє використовувати для кожної конструкції і покладається лише на бібліотеку 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));
}

Це працює з речами, які постачають rbegin () і rend (), а також зі статичними масивами.

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

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

0

Мені подобається зворотний ітератор в кінці Якка - відповідь Адама Неврамонта, але він здавався складним для того, що мені потрібно, тому я написав це:

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

Я можу взяти звичайний ітератор, такий:

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

і змініть його на це, щоб повторити в зворотному порядку:

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

0

Якщо ви можете використовувати бібліотеку Boost, існує Boost.Range, який забезпечує reverseадаптер діапазону , включаючи:

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

Потім, у поєднанні з діапазоном forциклу C ++ 11 , ви можете просто написати наступне:

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

Оскільки цей код коротший, ніж той, що використовує пару ітераторів, він може бути більш читабельним і менш схильним до помилок, оскільки на нього менше деталей, на які слід звертати увагу.


-1

використовувати цей код

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

-2

Оскільки я не хочу вводити новий синтаксис чужого Марса, і я просто хочу створити на основі існуючих примітивів, наведені нижче фрагменти працюють:

#include <vector>
#include <iostream>

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

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

    std::cout << "\n************\n";

    for (it = arr.end() - 1; it != arr.begin()-1;it--) {
        std::cout << *it << " ";
    }

    return 0;
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.