Lặp lại vectơ C ++ từ cuối đến đầu


96

Có thể lặp lại một vectơ từ cuối đến đầu không?

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

Hay điều đó chỉ có thể xảy ra với những thứ như vậy:

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

2
Trong C ++ 11 bạn có thể sử dụng phạm vi dựa trên cho vòng lặp với bộ chuyển đổi ngược lại, thấy ở đây
MM

1
về mặt lý thuyết, trên máy 32 bit, đối với giải pháp thứ hai, nếu kích thước vectơ lớn hơn 2.147.483.647 + 1, nó sẽ tràn (vectơ :: size () là không dấu), nhưng hiện tại có khả năng là bạn sẽ không bao giờ đạt đến giới hạn đó (cũng giới hạn vectơ hiện tại trên máy 32 bit là 1,073,741,823).
Stefan Rogin

Câu trả lời:


157

Cách tốt nhất là:

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

rbegin()/ rend()được thiết kế đặc biệt cho mục đích đó. (Và có, tăng một reverse_interatorsẽ di chuyển nó lùi lại.)

Bây giờ, về lý thuyết, phương pháp của bạn (sử dụng begin()/ end()& --i) sẽ hoạt động, std::vectortrình vòng lặp của bạn là hai chiều, nhưng hãy nhớ, end()không phải là phần tử cuối cùng - nó là phần tử nằm ngoài phần tử cuối cùng, vì vậy trước tiên bạn phải giảm và bạn hoàn thành khi bạn đạt đến begin()- nhưng bạn vẫn phải thực hiện quá trình xử lý của mình.

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

} 

CẬP NHẬT: Tôi dường như đã quá hung hăng trong việc viết lại for()vòng lặp thành một while()vòng lặp. (Phần quan trọng --ilà ở đầu.)


Tôi chỉ nhận ra rằng điều đó --isẽ gây ra một vấn đề lớn nếu vùng chứa trống ... Trước khi đi vào do - whilevòng lặp, bạn nên kiểm tra (my_vector.begin() != my_vector.end()).
a1ex07

1
Tại sao bạn sử dụng một do-whilevòng lặp thay vì chỉ một whilevòng lặp? Sau đó, bạn sẽ không cần bất kỳ kiểm tra đặc biệt nào cho các vectơ trống.
jamesdlin,

Bạn có thể cập nhật câu trả lời để sử dụng autocho dễ đọc hơn không?
LNJ

58

Nếu bạn có C ++ 11, bạn có thể sử dụng auto.

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

28

"Mô hình" được thiết lập tốt để lặp lại qua các phạm vi đóng mở trông như sau

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

hoặc, nếu bạn thích,

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

Ví dụ, mẫu này hữu ích để lập chỉ mục ngược một mảng bằng cách sử dụng chỉ mục không dấu

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

(Mọi người không quen thuộc với mô hình này thường nhấn mạnh về việc sử dụng chữ ký số nguyên kiểu cho mảng lập chỉ mục đặc biệt bởi vì họ tin rằng không đúng loại unsigned là bằng cách nào đó "không sử dụng được" cho chỉ mục ngược lại)

Nó có thể được sử dụng để lặp qua một mảng bằng kỹ thuật "con trỏ trượt"

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

hoặc nó có thể được sử dụng để lặp lại trên một vectơ bằng cách sử dụng một trình lặp thông thường (không đảo ngược)

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

cppreference.com cho biết, việc truy cập phần tử ở cuối () "dẫn đến hành vi không xác định", vì vậy tôi nghĩ, các vòng lặp nên bắt đầu từ--end()
Thomas Schmid

@ThomasSchmid Các vòng lặp này không bao giờ cố gắng truy cập tại end(). Mặc dù họ có vẻ bắt đầu từ lúc end(), họ luôn đảm bảo giảm trình lặp trước lần truy cập đầu tiên.
AnT

Điều này đẹp hơn rất nhiều sau đó rbegin / rend vì bạn có thể lặp lại theo cách khác trong thời gian chạy (không có mẫu) 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! xấu quá !. Bạn đang thử nghiệm reversed bốn lần - hai trong số chúng trong một vòng lặp. Tất nhiên, việc kiểm tra boolean rất nhanh, nhưng tại sao bạn không cần phải làm thế? Đặc biệt, vì mục đích duy nhất dường như là làm cho mã không thể đọc được. làm thế nào chúng tôi sử dụng hai vòng riêng biệt? 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

Trên thực tế, bạn đã bỏ lỡ quan điểm của tôi. Bạn hoàn toàn đúng khi chia nó thành hai ifs nhưng tôi muốn loại bỏ mẫu trên doStuff(). Vẫn có thể làm được mặc dù với hai ifs bạn có bằng cách lặp lại theo cách khác của vòng đầu tiên.
colin,

11

Bắt đầu với c ++ 20, bạn có thể sử dụng std::ranges::reverse_viewvòng lặp for dựa trên phạm vi:

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

Hoặc thậm chí

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

Thật không may, tại thời điểm viết bài (tháng 1 năm 2020) không có trình biên dịch chính nào triển khai thư viện phạm vi, nhưng bạn có thể sử dụng các phạm vi-v3 của 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

Trình rend() / rbegin()lặp người dùng :

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

Sau đó:

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

Ngoài ra trong C ++ 14 chỉ cần làm:

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

Trong C ++ 03/11 hầu hết các vùng chứa tiêu chuẩn đều có một .rbegin().rend()phương thức.

Cuối cùng, bạn có thể viết bộ điều hợp phạm vi backwardsnhư sau:

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

và bây giờ bạn có thể làm điều này:

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

mà tôi nghĩ là khá đẹp.



1

Đây là một triển khai siêu đơn giản cho phép sử dụng cho mỗi cấu trúc và chỉ dựa vào thư viện 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));
}

Điều này hoạt động với những thứ cung cấp rbegin () và rend (), cũng như với các mảng tĩnh.

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

Tôi thích trình lặp ngược ở cuối Yakk - câu trả lời của Adam Nevraumont, nhưng nó có vẻ phức tạp đối với những gì tôi cần, vì vậy tôi đã viết thế này:

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

Tôi có thể lấy một trình lặp bình thường như thế này:

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

và thay đổi nó thành này để lặp lại ngược lại:

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

0

Nếu bạn có thể sử dụng Thư viện Boost, có Boost.Range cung cấp reversebộ điều hợp phạm vi bằng cách bao gồm:

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

Sau đó, kết hợp với vòng lặp phạm vi của C ++ 11for , bạn chỉ có thể viết như sau:

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

Vì mã này ngắn hơn mã sử dụng cặp trình lặp, nó có thể dễ đọc hơn và ít bị lỗi hơn vì có ít chi tiết hơn cần chú ý.


-1

sử dụng mã này

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

Vì tôi không muốn giới thiệu cú pháp mới của người ngoài hành tinh sao Hỏa và tôi chỉ muốn xây dựng dựa trên các nguyên thủy hiện có, các đoạn mã dưới đây dường như hoạt động:

#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;
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.