การทำซ้ำเวกเตอร์ C ++ จากจุดสิ้นสุดถึงจุดเริ่มต้น


101

เป็นไปได้ไหมที่จะวนซ้ำเวกเตอร์จากจุดเริ่มต้นจนถึงจุดเริ่มต้น

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 บิตคือ 1,073,741,823)
Stefan Rogin

ปัญหาล้นของ @StefanRogin จะกลายเป็นจริงเมื่อแทนที่จะเป็น "int i" ใน for loop มีคนใช้ size_t (หรืออาจเป็นอัตโนมัติ) ในภารกิจเพื่อหลีกเลี่ยงคำเตือนของคอมไพเลอร์ (เนื่องจากการกำหนด size () ให้กับ int) ด้วยสิ่งนี้และสำหรับเวกเตอร์องค์ประกอบเดียวการวนซ้ำครั้งที่สองจะล้นอัตโนมัติ i และลูปจะดำเนินการด้วย "i" ที่มากเกินไปส่งผลให้เกิดข้อขัดข้องทุกประเภท
n-mam

คำตอบ:


164

วิธีที่ดีที่สุดคือ:

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

rbegin()/ rend()ถูกออกแบบมาโดยเฉพาะเพื่อจุดประสงค์นั้น (และใช่การเพิ่มการ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

60

หากคุณมี C ++ 11 คุณสามารถใช้ประโยชน์จากautoไฟล์.

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

30

"รูปแบบ" ที่ได้รับการยอมรับอย่างดีสำหรับการทำซ้ำย้อนกลับผ่านช่วงปิด - เปิดมีลักษณะดังนี้

// 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()
Thomas Schmid

1
@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

1
@ โคลินอีแกดส์! น่าเกลียดขนาดนั้น!. คุณกำลังทดสอบ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);}
James Curran

ที่จริงคุณพลาดประเด็นของฉัน คุณมีสิทธิ์ที่จะแบ่งมันออกเป็นสองส่วนifแต่ฉันต้องการกำจัดเทมเพลตในไฟล์doStuff(). ยังคงทำได้แม้ว่าจะมีสองifสิ่งที่คุณมีโดยการวนซ้ำอีกรอบในอันแรก
colin

17

เริ่มต้นด้วย c ++ 20 คุณสามารถใช้ a std::ranges::reverse_viewและ range-based for-loop:

#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) ไม่มีคอมไพเลอร์หลักที่ใช้ไลบรารี range แต่คุณสามารถใช้range-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;
}


6
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 คอนเทนเนอร์มาตรฐานส่วนใหญ่มี a .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

ฉันชอบตัววนรอบด้านหลังในตอนท้ายของ Yakk - คำตอบของ Adam Nevraumont แต่มันดูซับซ้อนสำหรับสิ่งที่ฉันต้องการดังนั้นฉันจึงเขียนสิ่งนี้:

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
}

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

1

หากคุณสามารถใช้ไลบรารี Boost ได้จะมีBoost.Rangeที่ให้reverseอะแดปเตอร์ช่วงโดยรวมถึง:

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

จากนั้นเมื่อใช้ร่วมกับrange- forloop ของ C ++ 11คุณสามารถเขียนสิ่งต่อไปนี้:

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

เนื่องจากรหัสนี้มีความซับซ้อนมากกว่ารหัสที่ใช้คู่ตัววนซ้ำจึงอาจอ่านได้มากกว่าและมีแนวโน้มที่จะเกิดข้อผิดพลาดน้อยกว่าเนื่องจากมีรายละเอียดที่ต้องใส่ใจน้อยกว่า


1
อันที่จริงboost::adaptors::reverseเป็นประโยชน์อย่างมาก!
Kai Petzke

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

1
รหัสนี้ล้มเหลวอย่างมากหากvecอ้างถึงเวกเตอร์ว่าง!
Kai Petzke

-2

เนื่องจากฉันไม่ต้องการแนะนำไวยากรณ์ C ++ ใหม่ที่เหมือนมนุษย์ต่างดาวและฉันแค่ต้องการสร้างจากพื้นฐานที่มีอยู่ตัวอย่างด้านล่างดูเหมือนจะใช้งานได้:

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

รหัสนี้ล้มเหลวอย่างมากหากarrอ้างถึงเวกเตอร์ว่าง!
Kai Petzke
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.