Iterando o vetor C ++ do fim ao começo


96

É possível iterar um vetor do fim ao começo?

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

Ou isso só é possível com algo assim:

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

2
No C ++ 11, você pode usar for-loop baseado em intervalo com adaptador reverso, veja aqui
MM

1
teoricamente, em uma máquina de 32 bits, para a segunda solução, se o tamanho do vetor for maior que 2.147.483.647 + 1, ele irá estourar (vector :: size () não tem sinal), mas atualmente as chances são de que você nunca vai atingir esse limite (também o limite do vetor atual em máquinas de 32 bits é 1.073.741.823).
Stefan Rogin

Respostas:


157

A melhor maneira é:

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

rbegin()/ rend()Foram especialmente concebidos para o efeito. (E sim, incrementar a reverse_interatormove para trás.)

Agora, em teoria, seu método (usando begin()/ end()& --i) funcionaria, std::vectoro iterador sendo bidirecional, mas lembre-se, end()não é o último elemento - é um além do último elemento, então você teria que decrementar primeiro, e você é feito quando você chegar begin()- mas você ainda terá que fazer o seu processamento.

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

} 

ATUALIZAÇÃO: Eu fui aparentemente muito agressivo ao reescrever o for()loop em um while()loop. (A parte importante é que o --iestá no início.)


Acabei de perceber que --iisso causará um grande problema se o recipiente estiver vazio ... Antes de entrar no do - whileloop, faz sentido verificar (my_vector.begin() != my_vector.end()).
a1ex07

1
Por que você está usando um do-whileloop em vez de apenas um whileloop? Então você não precisaria de nenhuma verificação especial para vetores vazios.
Jamesdlin

Você poderia atualizar a resposta a ser usada autopara melhor legibilidade?
LNJ

59

Se você tiver o C ++ 11, poderá usar auto.

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

29

O "padrão" bem estabelecido para a iteração reversa através de intervalos fechados-abertos é o seguinte

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

ou, se preferir,

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

Este padrão é útil, por exemplo, para indexação reversa de uma matriz usando um índice não assinado

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

(Pessoas não familiarizadas com este padrão muitas vezes insistem em usar tipos inteiros assinados para indexação de array, especificamente porque acreditam incorretamente que tipos não assinados são de alguma forma "inutilizáveis" para indexação reversa)

Ele pode ser usado para iterar sobre uma matriz usando uma técnica de "ponteiro deslizante"

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

ou pode ser usado para iteração reversa sobre um vetor usando um iterador comum (não reverso)

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

cppreference.com diz, acessar o elemento em end () "resulta em comportamento indefinido", então eu acho que os loops devem começar em--end()
Thomas Schmid

@ThomasSchmid Esses loops nunca tentam acessar em end(). Mesmo que pareçam começar em end(), eles sempre se certificam de diminuir o iterador antes do primeiro acesso.
Antes de

Isso é muito melhor do que rbegin / rend porque você pode fazer o loop de outra maneira em tempo de execução (sem modelo) 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! tão feio !. Você está testando reversed quatro vezes - duas delas dentro de um loop. Claro, testar um booleano é muito rápido, mas ainda assim, por que trabalhar você não precisa? Especialmente porque o único propósito parece ser tornar o código ilegível. que tal usarmos dois loops separados? 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

Na verdade você não entendeu meu ponto. Você está absolutamente certo em dividi-lo em dois ifs, mas eu queria me livrar do modelo no doStuff(). Ainda é possível, porém, com os dois ifs que você tem, fazendo o loop ao contrário no primeiro.
colin

11

Começando com c ++ 20, você pode usar um std::ranges::reverse_viewe um loop for baseado em intervalo:

#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 mesmo

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

Infelizmente, no momento da escrita (janeiro de 2020), nenhum grande compilador implementou a biblioteca de intervalos, mas você pode recorrer a intervalos-v3 de 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()Iteradores de usuário :

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

Então:

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

Como alternativa, em C ++ 14, basta fazer:

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

Em C ++ 03/11 a maioria dos recipientes convencionais têm uma .rbegin()e .rend()método bem.

Finalmente, você pode escrever o adaptador de alcance da backwardsseguinte maneira:

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 agora você pode fazer isso:

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

o que eu acho muito bonito.



1

Aqui está uma implementação super simples que permite o uso de for each construct e depende apenas da biblioteca 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));
}

Isso funciona com coisas que fornecem um rbegin () e rend (), bem como com matrizes estáticas.

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 você pode usar a Biblioteca Boost, há o Boost.Range que fornece o reverseadaptador de intervalo incluindo:

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

Então, em combinação com um loop de intervalo do C ++ 11for , você pode simplesmente escrever o seguinte:

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

Como esse código é mais breve do que aquele que usa o par de iteradores, ele pode ser mais legível e menos sujeito a erros, pois há menos detalhes aos quais prestar atenção.


1
Na verdade, boost::adaptors::reverseé muito útil!
Kai Petzke

0

Eu gosto do iterador reverso no final de Yakk - a resposta de Adam Nevraumont, mas parecia complicado para o que eu precisava, então escrevi isto:

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

Sou capaz de usar um iterador normal como este:

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

e altere-o para iterar ao contrário:

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

-1

use este código

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

Este código falha terrivelmente, se se vecrefere a um vetor vazio!
Kai Petzke

-2

Como não quero introduzir a nova sintaxe alienígena de Marte, e simplesmente quero construir sobre primitivos existentes, os snippets abaixo parecem funcionar:

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

Este código falha terrivelmente, se se arrrefere a um vetor vazio!
Kai Petzke
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.