ทำความสะอาดวิธีการเขียนลูป 'for' หลาย ๆ อัน


98

สำหรับอาร์เรย์ที่มีหลายมิติโดยปกติเราจะต้องเขียนforลูปสำหรับแต่ละมิติ ตัวอย่างเช่น:

vector< vector< vector<int> > > A;

for (int k=0; k<A.size(); k++)
{
    for (int i=0; i<A[k].size(); i++)
    {
        for (int j=0; j<A[k][i].size(); j++)
        {
            do_something_on_A(A[k][i][j]);
        }
    }
}

double B[10][8][5];
for (int k=0; k<10; k++)
{
    for (int i=0; i<8; i++)
    {
        for (int j=0; j<5; j++)
        {
            do_something_on_B(B[k][i][j]);
        }
    }
}

คุณจะเห็นfor-for-forลูปแบบนี้ในโค้ดของเราบ่อยๆ ฉันจะใช้มาโครเพื่อกำหนดfor-for-forลูปได้อย่างไรเพื่อที่ฉันจะได้ไม่ต้องเขียนโค้ดประเภทนี้ซ้ำทุกครั้ง มีวิธีที่ดีกว่านี้หรือไม่?


62
คำตอบที่ชัดเจนคือคุณไม่ทำ คุณไม่ได้สร้างภาษาใหม่โดยใช้มาโคร (หรือเทคนิคอื่นใด) คนที่มาตามคุณจะไม่สามารถอ่านรหัสได้
James Kanze

17
เมื่อคุณมีเวกเตอร์ที่เป็นเวกเตอร์ของเวกเตอร์นั่นเป็นสัญญาณของการออกแบบที่ไม่ดี
Maroun

5
@Nim: คุณสามารถทำได้ด้วย1 flat array (ไม่แน่ใจว่าดีกว่า)
จรด 42

16
ฉันคิดว่าคุณคงไม่ต้องการซ่อนO(n) = n^3รหัสที่เป็นไปได้ ...
poy

36
@ TC1: แล้วฉันจะพบว่ามันยากที่จะอ่าน ทั้งหมดนี้เป็นคำถามเกี่ยวกับความชอบส่วนบุคคลและจริงๆแล้วมันไม่ได้ช่วยอะไรกับคำถามตรงนี้
เมื่อ

คำตอบ:


281

สิ่งแรกคือคุณไม่ได้ใช้โครงสร้างข้อมูลดังกล่าว หากคุณต้องการเมทริกซ์สามมิติคุณต้องกำหนดหนึ่ง:

class Matrix3D
{
    int x;
    int y;
    int z;
    std::vector<int> myData;
public:
    //  ...
    int& operator()( int i, int j, int k )
    {
        return myData[ ((i * y) + j) * z + k ];
    }
};

หรือถ้าคุณต้องการจัดทำดัชนีโดยใช้[][][]คุณต้องมีoperator[] ที่ส่งคืนพร็อกซี

เมื่อคุณทำสิ่งนี้เสร็จแล้วหากคุณพบว่าคุณต้องทำซ้ำอย่างต่อเนื่องตามที่ได้นำเสนอคุณจะต้องแสดงตัวทำซ้ำซึ่งจะรองรับ:

class Matrix3D
{
    //  as above...
    typedef std::vector<int>::iterator iterator;
    iterator begin() { return myData.begin(); }
    iterator end()   { return myData.end();   }
};

จากนั้นคุณก็เขียน:

for ( Matrix3D::iterator iter = m.begin(); iter != m.end(); ++ iter ) {
    //  ...
}

(หรือเพียงแค่:

for ( auto& elem: m ) {
}

ถ้าคุณมี C ++ 11.)

และหากคุณต้องการดัชนีทั้งสามในระหว่างการทำซ้ำดังกล่าวคุณสามารถสร้างตัววนซ้ำซึ่งแสดงให้เห็น:

class Matrix3D
{
    //  ...
    class iterator : private std::vector<int>::iterator
    {
        Matrix3D const* owner;
    public:
        iterator( Matrix3D const* owner,
                  std::vector<int>::iterator iter )
            : std::vector<int>::iterator( iter )
            , owner( owner )
        {
        }
        using std::vector<int>::iterator::operator++;
        //  and so on for all of the iterator operations...
        int i() const
        {
            ((*this) -  owner->myData.begin()) / (owner->y * owner->z);
        }
        //  ...
    };
};

21
คำตอบนี้ควรได้รับการโหวตมากขึ้นเนื่องจากเป็นคำตอบเดียวที่เกี่ยวข้องกับแหล่งที่มาที่แท้จริงของปัญหา
ก่อน

5
อาจเป็นคำตอบที่ไม่ถูกต้อง แต่ฉันไม่เห็นด้วยว่ามันเป็นคำตอบที่ดี โค้ดเทมเพลตที่เป็นความลับจำนวนมากซึ่งอาจใช้เวลาคอมไพล์ช้า x10 เท่าและโค้ดดีบักช้า x10 สำหรับฉันแล้วรหัสต้นฉบับนั้นชัดเจนกว่าสำหรับฉัน ...
Gorkem

10
@beehorf ... และยังช้ากว่ามาก เนื่องจากอาร์เรย์หลายมิติใน C และ C ++ เป็นอาร์เรย์ที่ซ้อนกันจริงในแง่ที่มิติภายนอกจัดเก็บพอยน์เตอร์ไปยังอาร์เรย์ที่ซ้อนกัน จากนั้นอาร์เรย์ที่ซ้อนกันเหล่านี้จะกระจัดกระจายไปทั่วในหน่วยความจำโดยพลการเอาชนะการดึงข้อมูลล่วงหน้าและการแคชได้อย่างมีประสิทธิภาพ ฉันรู้จักตัวอย่างที่ใครบางคนเขียนโค้ดโดยใช้vector<vector<vector<double> > >แทนฟิลด์ 3 มิติ การเขียนโค้ดใหม่ให้เทียบเท่ากับโซลูชันข้างต้นส่งผลให้มีการเพิ่มความเร็วเป็น 10
Michael Wild

5
@beehorf คุณเห็นรหัสเทมเพลตที่ไหน (ในทางปฏิบัติMatrix3Dอาจเป็นเทมเพลต แต่เป็นเทมเพลตที่ตรงไปตรงมามาก) และคุณจะต้องแก้จุดบกพร่องMatrix3Dเท่านั้นไม่ใช่ทุกครั้งที่คุณต้องการเมทริกซ์ 3 มิติดังนั้นคุณจึงประหยัดเวลาได้มากในการดีบัก สำหรับความชัดเจน: std::vector<std::vector<std::vector<int>>>ชัดเจนกว่าMatrix3Dอย่างไร? ไม่ต้องพูดถึงการMatrix3Dบังคับใช้ข้อเท็จจริงที่ว่าคุณมีเมทริกซ์ในขณะที่เวกเตอร์ที่ซ้อนกันอาจมอมแมมและข้างต้นอาจเร็วกว่ามาก
James Kanze

10
@MichaelWild แต่แน่นอนข้อดีที่แท้จริงของแนวทางของฉันคือคุณสามารถเปลี่ยนการเป็นตัวแทนขึ้นอยู่กับว่าอะไรจะเร็วกว่าในสภาพแวดล้อมของคุณโดยไม่ต้องแก้ไขโค้ดไคลเอนต์ใด ๆ กุญแจสู่ประสิทธิภาพที่ดีคือการห่อหุ้มที่เหมาะสมเพื่อให้คุณสามารถทำการเปลี่ยนแปลงตามที่ผู้สร้างโปรไฟล์ระบุว่าคุณต้องการโดยไม่ต้องเขียนแอปพลิเคชันใหม่ทั้งหมด
James Kanze

44

การใช้มาโครเพื่อซ่อนforลูปอาจทำให้สับสนได้มากเพียงเพื่อประหยัดอักขระเพียงไม่กี่ตัว ฉันจะใช้range-for loops แทน:

for (auto& k : A)
    for (auto& i : k)
        for (auto& j : i)
            do_something_on_A(j);

แน่นอนคุณสามารถแทนที่auto&ด้วยconst auto&ถ้าคุณจริงแล้วไม่ได้แก้ไขข้อมูล


3
สมมติว่า OP สามารถใช้ C ++ 11
จรด 42

1
@herohuyongtao ในกรณีของ iterators. ซึ่งอาจเป็นสำนวนมากกว่าที่นี่ แต่มีบางกรณีที่คุณต้องการintตัวแปรสามตัว
James Kanze

1
และไม่ควรเป็นเช่นนั้นdo_something_on_A(*j)?
James Kanze

1
@Jefffrey อ่าใช่ อีกเหตุผลหนึ่งในการสะกดประเภท (ฉันเดาว่าการใช้autoสำหรับkและiอาจจะเป็นธรรมยกเว้นว่ามันยังแก้ปัญหาในระดับที่ไม่ถูกต้องปัญหาที่แท้จริงคือเขาใช้เวกเตอร์ที่ซ้อนกัน)
James Kanze

2
@Dhara kเป็นเวกเตอร์ทั้งหมดของเวกเตอร์ (อ้างอิงได้ดี) ไม่ใช่ดัชนี
Yakk - Adam Nevraumont

21

สิ่งนี้สามารถช่วยได้:

 template <typename Container, typename Function>
 void for_each3d(const Container &container, Function function)
 {
     for (const auto &i: container)
         for (const auto &j: i)
             for (const auto &k: j)
                 function(k);
 }

 int main()
 {
     vector< vector< vector<int> > > A;     
     for_each3d(A, [](int i){ std::cout << i << std::endl; });

     double B[10][8][5] = { /* ... */ };
     for_each3d(B, [](double i){ std::cout << i << std::endl; });
 }

เพื่อที่จะทำให้เป็น N-ary เราจำเป็นต้องมีเวทมนตร์แม่แบบ ก่อนอื่นเราควรสร้างโครงสร้าง SFINAE เพื่อแยกแยะว่าค่านี้หรือคอนเทนเนอร์ การใช้งานดีฟอลต์สำหรับค่าและความเชี่ยวชาญสำหรับอาร์เรย์และคอนเทนเนอร์แต่ละประเภท วิธีที่ @Zeta บันทึกเราสามารถกำหนดคอนเทนเนอร์มาตรฐานตามiteratorประเภทที่ซ้อนกันได้(ควรตรวจสอบว่าประเภทสามารถใช้กับ range-base forได้หรือไม่)

 template <typename T>
 struct has_iterator
 {
     template <typename C>
     constexpr static std::true_type test(typename C::iterator *);

     template <typename>
     constexpr static std::false_type test(...);

     constexpr static bool value = std::is_same<
         std::true_type, decltype(test<typename std::remove_reference<T>::type>(0))
     >::value;
 };

 template <typename T>
 struct is_container : has_iterator<T> {};

 template <typename T>
 struct is_container<T[]> : std::true_type {};

 template <typename T, std::size_t N>
 struct is_container<T[N]> : std::true_type {}; 

 template <class... Args>
 struct is_container<std::vector<Args...>> : std::true_type {};

การนำไปใช้งาน for_eachนั้นตรงไปตรงมา ฟังก์ชันเริ่มต้นจะเรียกfunction:

 template <typename Value, typename Function>
 typename std::enable_if<!is_container<Value>::value, void>::type
 rfor_each(const Value &value, Function function)
 {
     function(value);
 }

และความเชี่ยวชาญจะเรียกตัวเองซ้ำ:

 template <typename Container, typename Function>
 typename std::enable_if<is_container<Container>::value, void>::type
 rfor_each(const Container &container, Function function)
 {
     for (const auto &i: container)
         rfor_each(i, function);
 }

และ voila:

 int main()
 {
     using namespace std;
     vector< vector< vector<int> > > A;
     A.resize(3, vector<vector<int> >(3, vector<int>(3, 5)));
     rfor_each(A, [](int i){ std::cout << i << ", "; });
     // 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,

     std::cout << std::endl;
     double B[3][3] = { { 1. } };
     rfor_each(B, [](double i){ std::cout << i << ", "; });
     // 1, 0, 0, 0, 0, 0, 0, 0, 0,
 }

นอกจากนี้สิ่งนี้จะใช้ไม่ได้กับตัวชี้ (อาร์เรย์ที่จัดสรรในฮีป)


@herohuyongtao ด้วยข้อ จำกัด เราสามารถใช้สองความเชี่ยวชาญสำหรับContainerและสำหรับคนอื่น ๆ
fasked

1
@herohuyongtao ฉันทำตัวอย่างของ K-ary foreach
fasked

1
@fasked: ใช้is_container : has_iterator<T>::valueจากคำตอบของฉันและคุณไม่จำเป็นต้องเขียนความเชี่ยวชาญสำหรับทุกประเภทเนื่องจากทุกคอนเทนเนอร์ควรมีiteratortypedef อย่าลังเลที่จะใช้อะไรก็ได้จากคำตอบของฉันของคุณดีกว่าอยู่แล้ว
Zeta

@ ซีต้า +1 นี้. ตามที่ฉันกล่าวถึงContainerแนวคิดจะช่วยได้
fasked

::iteratorไม่ใช่ช่วงที่ทำซ้ำได้ int x[2][3][4]สามารถทำซ้ำได้อย่างสมบูรณ์แบบเนื่องจากstruct foo { int x[3]; int* begin() { return x; } int* end() { return x+3; } }; ฉันไม่แน่ใจว่าT[]ควรทำอะไรเป็นพิเศษ?
Yakk - Adam Nevraumont

17

คำตอบส่วนใหญ่แสดงให้เห็นว่า C ++ สามารถบิดเป็นส่วนขยายวากยสัมพันธ์ที่เข้าใจยากได้อย่างไร IMHO

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

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

for (auto& k : A)
for (auto& i : k)
for (auto& current_A : i)
    do_something_on_A(current_A);

สอดคล้องกับนิยามที่คลุมเครือของเวกเตอร์เวกเตอร์ของเวกเตอร์ของ int โดยไม่มีความหมายที่ชัดเจน


10
#include "stdio.h"

#define FOR(i, from, to)    for(int i = from; i < to; ++i)
#define TRIPLE_FOR(i, j, k, i_from, i_to, j_from, j_to, k_from, k_to)   FOR(i, i_from, i_to) FOR(j, j_from, j_to) FOR(k, k_from, k_to)

int main()
{
    TRIPLE_FOR(i, j, k, 0, 3, 0, 4, 0, 2)
    {
        printf("i: %d, j: %d, k: %d\n", i, j, k);
    }
    return 0;
}

อัปเดต: ฉันรู้ว่าคุณขอ แต่คุณไม่ควรใช้สิ่งนั้น :)


5
ฉันรู้ว่านั่นคือสิ่งที่ OP ขอ แต่อย่างจริงจัง ... นี่ดูเหมือนเป็นตัวอย่างที่น่าอัศจรรย์ของความสับสน สมมติว่าTRIPLE_FORมีการกำหนดไว้ในส่วนหัวบางส่วนฉันจะคิดอย่างไรเมื่อเห็น `TRIPLE_FOR ที่นี่
James Kanze

2
ใช่ฉันเดาว่าคุณพูดถูก :) ฉันคิดว่าฉันจะปล่อยไว้ที่นี่เป็นเพียงตัวอย่างที่สามารถทำได้โดยใช้มาโคร แต่เพิ่มบันทึกว่าจะดีกว่าที่จะไม่ทำแบบนั้น :) ฉันเพิ่งตื่น ขึ้นและตัดสินใจใช้คำถามนี้เป็นการอุ่นเครื่องเล็ก ๆ น้อย ๆ สำหรับจิตใจ
FreeNickname

5

แนวคิดอย่างหนึ่งคือการเขียนคลาส pseudo-container ที่ทำซ้ำได้ซึ่ง "มี" ชุดของ tuples หลายดัชนีทั้งหมดที่คุณจะจัดทำดัชนี ไม่มีการนำไปใช้ที่นี่เพราะจะใช้เวลานานเกินไป แต่แนวคิดก็คือคุณควรจะเขียนได้ ...

multi_index mi (10, 8, 5);
  //  The pseudo-container whose iterators give {0,0,0}, {0,0,1}, ...

for (auto i : mi)
{
  //  In here, use i[0], i[1] and i[2] to access the three index values.
}

คำตอบที่ดีที่สุดที่นี่ imo
davidhigh

4

ฉันเห็นคำตอบมากมายที่นี่ซึ่งทำงานแบบวนซ้ำโดยตรวจสอบว่าอินพุตเป็นคอนเทนเนอร์หรือไม่ แต่ทำไมไม่ตรวจสอบว่าเลเยอร์ปัจจุบันเป็นประเภทเดียวกับที่ฟังก์ชันใช้หรือไม่ ง่ายกว่ามากและช่วยให้สามารถใช้งานฟังก์ชันที่มีประสิทธิภาพมากขึ้น

//This is roughly what we want for values
template<class input_type, class func_type> 
void rfor_each(input_type&& input, func_type&& func) 
{ func(input);}

//This is roughly what we want for containers
template<class input_type, class func_type>
void rfor_each(input_type&& input, func_type&& func) 
{ for(auto&& i : input) rfor_each(i, func);}

อย่างไรก็ตามสิ่งนี้ (ชัดเจน) ทำให้เรามีข้อผิดพลาดที่ไม่ชัดเจน ดังนั้นเราจึงใช้ SFINAE เพื่อตรวจสอบว่าอินพุตปัจจุบันตรงกับฟังก์ชันหรือไม่

//Compiler knows to only use this if it can pass input to func
template<class input_type, class func_type>
auto rfor_each(input_type&& input, func_type&& func) ->decltype(func(input)) 
{ return func(input);}

//Otherwise, it always uses this one
template<class input_type, class func_type>
void rfor_each(input_type&& input, func_type&& func) 
{ for(auto&& i : input) rfor_each(i, func);}

ตอนนี้จัดการกับคอนเทนเนอร์ได้อย่างถูกต้อง แต่คอมไพเลอร์ยังคงพิจารณาว่าสิ่งนี้ไม่ชัดเจนสำหรับ input_types ที่สามารถส่งผ่านไปยังฟังก์ชันได้ ดังนั้นเราจึงใช้เคล็ดลับ C ++ 03 มาตรฐานเพื่อทำให้มันชอบฟังก์ชันแรกมากกว่าฟังก์ชันที่สองจากการส่งผ่านศูนย์และทำให้อันที่เราต้องการยอมรับและ int และอีกอันใช้ ...

template<class input_type, class func_type>
auto rfor_each(input_type&& input, func_type&& func, int) ->decltype(func(input)) 
{ return func(input);}

//passing the zero causes it to look for a function that takes an int
//and only uses ... if it absolutely has to 
template<class input_type, class func_type>
void rfor_each(input_type&& input, func_type&& func, ...) 
{ for(auto&& i : input) rfor_each(i, func, 0);}

แค่นั้นแหละ. รหัสหกบรรทัดที่ค่อนข้างเรียบง่ายและคุณสามารถวนซ้ำค่าแถวหรือหน่วยย่อยอื่น ๆ ได้ซึ่งแตกต่างจากคำตอบอื่น ๆ ทั้งหมด

#include <iostream>
int main()
 {

     std::cout << std::endl;
     double B[3][3] = { { 1.2 } };
     rfor_each(B[1], [](double&v){v = 5;}); //iterate over doubles
     auto write = [](double (&i)[3]) //iterate over rows
         {
             std::cout << "{";
             for(double d : i) 
                 std::cout << d << ", ";
             std::cout << "}\n";
         };
     rfor_each(B, write );
 };

หลักฐานการรวบรวมและการดำเนินการที่นี่และที่นี่

หากคุณต้องการไวยากรณ์ที่สะดวกกว่าใน C ++ 11 คุณสามารถเพิ่มมาโครได้ (ต่อไปนี้ยังไม่ทดลอง)

template<class container>
struct container_unroller {
    container& c;
    container_unroller(container& c_) :c(c_) {}
    template<class lambda>
    void operator <=(lambda&& l) {rfor_each(c, l);}
};
#define FOR_NESTED(type, index, container) container_unroller(container) <= [](type& index) 
//note that this can't handle functions, function pointers, raw arrays, or other complex bits

int main() {
     double B[3][3] = { { 1.2 } };
     FOR_NESTED(double, v, B) {
         std::cout << v << ", ";
     }
}

3

ฉันขอเตือนคำตอบนี้ด้วยคำสั่งต่อไปนี้: สิ่งนี้จะใช้ได้เฉพาะเมื่อคุณใช้งานอาร์เรย์จริง - มันจะไม่ทำงานสำหรับตัวอย่างของคุณโดยใช้std::vector.

หากคุณกำลังดำเนินการแบบเดียวกันในทุกองค์ประกอบของอาร์เรย์หลายมิติโดยไม่สนใจตำแหน่งของแต่ละรายการคุณสามารถใช้ประโยชน์จากข้อเท็จจริงที่ว่าอาร์เรย์ถูกวางไว้ในตำแหน่งหน่วยความจำที่อยู่ติดกันและถือว่าทั้งสิ่งเป็นหนึ่งเดียวกัน อาร์เรย์หนึ่งมิติขนาดใหญ่ ตัวอย่างเช่นหากเราต้องการคูณทุกองค์ประกอบด้วย 2.0 ในตัวอย่างที่สองของคุณ:

double B[3][3][3];
// ... set the values somehow
double* begin = &B[0][0][0];     // get a pointer to the first element
double* const end = &B[3][0][0]; // get a (const) pointer past the last element
for (; end > begin; ++begin) {
    (*begin) *= 2.0;
}

โปรดทราบว่าการใช้แนวทางข้างต้นยังช่วยให้สามารถใช้เทคนิค C ++ ที่ "เหมาะสม" ได้ด้วย:

double do_something(double d) {
    return d * 2.0;
}

...

double B[3][3][3];
// ... set the values somehow
double* begin = &B[0][0][0];  // get a pointer to the first element
double* end = &B[3][0][0];    // get a pointer past the last element

std::transform(begin, end, begin, do_something);

โดยทั่วไปฉันไม่แนะนำวิธีนี้ (เลือกที่คล้ายกับคำตอบของเจฟฟรีย์) เนื่องจากต้องอาศัยการกำหนดขนาดสำหรับอาร์เรย์ของคุณ แต่ในบางกรณีอาจมีประโยชน์


สิ่งนี้ผิดกฎหมาย: stackoverflow.com/questions/6015080/…
ecatmur

@ecatmur: น่าสนใจ - ฉันเพิ่งทำงานได้ดังนั้นฉันจะตรวจสอบสิ่งนี้และอัปเดต / ลบคำตอบตามนั้น ขอบคุณ.
icabod

@ecatmur: ฉันได้ดูมาตรฐาน C ++ 11 (หัวข้อ 8.3.4) แล้วสิ่งที่ฉันเขียนควรใช้งานได้และไม่ดูผิดกฎหมาย (สำหรับฉัน) ลิงก์ที่คุณระบุเกี่ยวข้องกับการเข้าถึงสมาชิกนอกขนาดอาร์เรย์ที่กำหนดไว้ แม้ว่าจะเป็นความจริงที่ว่าฉันได้รับที่อยู่เพียงแค่ผ่านอาร์เรย์ แต่ก็ไม่ได้เข้าถึงข้อมูล - นี่คือเพื่อให้ "สิ้นสุด" ในลักษณะเดียวกับที่คุณสามารถใช้พอยน์เตอร์เป็นตัวทำซ้ำโดยที่ "end" เป็นหนึ่งในอดีต องค์ประกอบสุดท้าย
icabod

คุณเข้าถึงได้อย่างมีประสิทธิภาพB[0][0][i]สำหรับi >= 3; สิ่งนี้ไม่ได้รับอนุญาตเนื่องจากกำลังเข้าถึงภายนอกอาร์เรย์ (ภายใน)
ecatmur

1
วิธีที่ชัดเจนกว่า IMO ในการกำหนดจุดสิ้นสุดหากคุณต้องทำสิ่งนี้คือ end = start + (xSize * ySize * zSize)
noggin182

2

ฉันรู้สึกตกใจมากที่ไม่มีใครเสนอลูปที่ใช้เวทย์มนตร์เลขคณิตให้ทำงาน เนื่องจากC. Wangกำลังมองหาวิธีแก้ปัญหาโดยไม่มีลูปซ้อนกันฉันจะเสนอวิธีหนึ่ง:

double B[10][8][5];
int index = 0;

while (index < (10 * 8 * 5))
{
    const int x = index % 10,
              y = (index / 10) % 10,
              z = index / 100;

    do_something_on_B(B[x][y][z]);
    ++index;
}

วิธีนี้ไม่หรูหราและยืดหยุ่นดังนั้นเราจึงสามารถรวมกระบวนการทั้งหมดลงในฟังก์ชันเทมเพลต:

template <typename F, typename T, int X, int Y, int Z>
void iterate_all(T (&xyz)[X][Y][Z], F func)
{
    const int limit = X * Y * Z;
    int index = 0;

    while (index < limit)
    {
        const int x = index % X,
                  y = (index / X) % Y,
                  z = index / (X * Y);

        func(xyz[x][y][z]);
        ++index;
    }
}

ฟังก์ชันเทมเพลตนี้สามารถแสดงในรูปแบบของลูปที่ซ้อนกันได้เช่นกัน:

template <typename F, typename T, int X, int Y, int Z>
void iterate_all(T (&xyz)[X][Y][Z], F func)
{
    for (auto &yz : xyz)
    {
        for (auto &z : yz)
        {
            for (auto &v : z)
            {
                func(v);
            }
        }
    }
}

และสามารถใช้จัดเตรียมอาร์เรย์ 3 มิติที่มีขนาดโดยพลการบวกกับชื่อฟังก์ชันการปล่อยให้การหักพารามิเตอร์ทำงานอย่างหนักในการนับขนาดของแต่ละมิติ:

int main()
{
    int A[10][8][5] = {{{0, 1}, {2, 3}}, {{4, 5}, {6, 7}}};
    int B[7][99][8] = {{{0, 1}, {2, 3}}, {{4, 5}, {6, 7}}};

    iterate_all(A, do_something_on_A);
    iterate_all(B, do_something_on_B);

    return 0;
}

ไปสู่ทั่วไปมากขึ้น

แต่อีกครั้งมันขาดความยืดหยุ่นเพราะมันใช้งานได้กับอาร์เรย์ 3 มิติเท่านั้น แต่เมื่อใช้ SFINAE เราสามารถทำงานกับอาร์เรย์ของมิติตามอำเภอใจได้ก่อนอื่นเราต้องมีฟังก์ชันเทมเพลตที่วนซ้ำอาร์เรย์ของอันดับ 1:

template<typename F, typename A>
typename std::enable_if< std::rank<A>::value == 1 >::type
iterate_all(A &xyz, F func)
{
    for (auto &v : xyz)
    {
        func(v);
    }
}

และอีกอันหนึ่งที่วนซ้ำอาร์เรย์ของอันดับใด ๆ โดยทำการเรียกซ้ำ:

template<typename F, typename A>
typename std::enable_if< std::rank<A>::value != 1 >::type
iterate_all(A &xyz, F func)
{
    for (auto &v : xyz)
    {
        iterate_all(v, func);
    }
}

สิ่งนี้ช่วยให้เราสามารถวนซ้ำองค์ประกอบทั้งหมดในทุกมิติของอาร์เรย์ขนาดโดยพลการ


ทำงานกับ std::vector

สำหรับเวกเตอร์ที่ซ้อนกันหลายตัวโซลูชันจะมีลักษณะคล้ายกับอาร์เรย์ที่กำหนดขนาดตามอำเภอใจ แต่ไม่มี SFINAE ขั้นแรกเราจะต้องมีฟังก์ชันเทมเพลตที่วนซ้ำstd::vectorและเรียกฟังก์ชันที่ต้องการ:

template <typename F, typename T, template<typename, typename> class V>
void iterate_all(V<T, std::allocator<T>> &xyz, F func)
{
    for (auto &v : xyz)
    {
        func(v);
    }
}

และฟังก์ชันแม่แบบอื่นที่วนซ้ำเวกเตอร์ชนิดใดก็ได้และเรียกตัวเองว่า:

template <typename F, typename T, template<typename, typename> class V> 
void iterate_all(V<V<T, std::allocator<T>>, std::allocator<V<T, std::allocator<T>>>> &xyz, F func)
{
    for (auto &v : xyz)
    {
        iterate_all(v, func);
    }
}

โดยไม่คำนึงถึงระดับการซ้อนกันiterate_allจะเรียกเวอร์ชันเวกเตอร์ของเวกเตอร์เว้นแต่เวอร์ชันเวกเตอร์ของค่าจะจับคู่ได้ดีกว่าจึงสิ้นสุดการเรียกซ้ำ

int main()
{
    using V0 = std::vector< std::vector< std::vector<int> > >;
    using V1 = std::vector< std::vector< std::vector< std::vector< std::vector<int> > > > >;

    V0 A0 =   {{{0, 1}, {2, 3}}, {{4, 5}, {6, 7}}};
    V1 A1 = {{{{{9, 8}, {7, 6}}, {{5, 4}, {3, 2}}}}};

    iterate_all(A0, do_something_on_A);
    iterate_all(A1, do_something_on_A);

    return 0;
}

ฉันคิดว่าเนื้อหาของฟังก์ชันค่อนข้างเรียบง่ายและตรงไปตรงมา ... ฉันสงสัยว่าคอมไพเลอร์สามารถคลายการวนซ้ำนี้ได้หรือไม่ (ฉันเกือบแน่ใจว่าคอมไพเลอร์ส่วนใหญ่สามารถคลายตัวอย่างแรกได้)

ดูการสาธิตสดที่นี่

หวังว่าจะช่วยได้


1

ใช้บางสิ่งตามบรรทัดเหล่านี้ (รหัสหลอก แต่แนวคิดยังคงเหมือนเดิม) คุณแยกรูปแบบเพื่อวนซ้ำหนึ่งครั้งและใช้ฟังก์ชันที่แตกต่างกันในแต่ละครั้ง

doOn( structure A, operator o)
{
    for (int k=0; k<A.size(); k++)
    {
            for (int i=0; i<A[k].size(); i++)
            {
                for (int j=0; j<A[k][i].size(); j++)
                {
                        o.actOn(A[k][i][j]);
                }
            }
    }
}

doOn(a, function12)
doOn(a, function13)

1

ติดกับที่ซ้อนกันสำหรับลูป!

วิธีการทั้งหมดที่แนะนำในที่นี้มีข้อเสียในแง่ของความสามารถในการอ่านหรือความยืดหยุ่น

จะเกิดอะไรขึ้นถ้าคุณต้องใช้ผลลัพธ์ของวงในสำหรับการประมวลผลในวงนอก? จะเกิดอะไรขึ้นถ้าคุณต้องการค่าจากวงนอกภายในวงในของคุณ? วิธีการ "ห่อหุ้ม" ส่วนใหญ่ล้มเหลวที่นี่

เชื่อฉันว่าฉันได้เห็นความพยายามหลายครั้งในการ "ล้างข้อมูล" ที่ซ้อนกันสำหรับลูปและในที่สุดปรากฎว่าลูปที่ซ้อนกันเป็นโซลูชันที่สะอาดและยืดหยุ่นที่สุด


0

เทคนิคหนึ่งที่ฉันใช้คือเทมเพลต เช่น:

template<typename T> void do_something_on_A(std::vector<T> &vec) {
    for (auto& i : vec) { // can use a simple for loop in C++03
        do_something_on_A(i);
    }
}

void do_something_on_A(int &val) {
    // this is where your `do_something_on_A` method goes
}

จากนั้นคุณก็โทรdo_something_on_A(A)เข้ารหัสหลักของคุณ ฟังก์ชันเทมเพลตจะถูกสร้างขึ้นหนึ่งครั้งสำหรับแต่ละมิติครั้งแรกกับT = std::vector<std::vector<int>>ครั้งที่สองด้วยด้วยT = std::vector<int>ครั้งที่สองด้วยกับ

คุณสามารถทำให้สิ่งนี้เป็นแบบทั่วไปมากขึ้นโดยใช้std::function(หรือวัตถุคล้ายฟังก์ชันใน C ++ 03) เป็นอาร์กิวเมนต์ที่สองหากคุณต้องการ:

template<typename T> void do_something_on_vec(std::vector<T> &vec, std::function &func) {
    for (auto& i : vec) { // can use a simple for loop in C++03
        do_something_on_vec(i, func);
    }
}

template<typename T> void do_something_on_vec(T &val, std::function &func) {
    func(val);
}

จากนั้นเรียกมันว่า:

do_something_on_vec(A, std::function(do_something_on_A));

วิธีนี้ใช้งานได้แม้ว่าฟังก์ชันจะมีลายเซ็นเดียวกันเนื่องจากฟังก์ชันแรกตรงกับสิ่งที่std::vectorอยู่ในประเภทมากกว่า


0

คุณสามารถสร้างดัชนีในวงเดียวเช่นนี้ (A, B, C คือมิติข้อมูล):

int A = 4, B = 3, C = 3;
for(int i=0; i<A*B*C; ++i)
{
    int a = i/(B*C);
    int b = (i-((B*C)*(i/(B*C))))/C;
    int c = i%C;
}

ผมเห็นด้วยกับคุณได้รับการออกแบบมาโดยเฉพาะสำหรับ 3 มิติ)
janek

1
ไม่ต้องพูดถึงมันช้าอย่างไม่น่าเชื่อ!
noggin182

@ noggin182: คำถามไม่ได้เกี่ยวกับความเร็ว แต่เกี่ยวกับการหลีกเลี่ยงการซ้อนสำหรับลูป นอกจากนี้ยังมีหน่วยงานที่ไม่จำเป็นอยู่ที่นั่น i / (B * C) สามารถแทนที่ได้ด้วย a
janek

ตกลงนี่เป็นอีกทางเลือกหนึ่งซึ่งน่าจะมีประสิทธิภาพมากกว่า (javascript): สำหรับ (var i = 0, j = 0, k = 0; i <A; i + = (j == B-1 && k == C - 1)? 1: 0, j = (k == C - 1)? ((j == B-1)? 0: j + 1): j, k = (k == C - 1)? 0: k + 1) {console.log (i + "" + j + "" + k); }
59

0

สิ่งหนึ่งที่คุณอาจต้องการลองหากคุณมีเพียงข้อความในวงในสุด - และข้อกังวลของคุณเกี่ยวกับลักษณะที่ละเอียดเกินไปของรหัสคือการใช้รูปแบบช่องว่างที่แตกต่างกัน วิธีนี้จะใช้ได้ผลก็ต่อเมื่อคุณสามารถระบุสำหรับลูปของคุณให้กระชับเพียงพอเพื่อให้พอดีกับบรรทัดเดียว

สำหรับตัวอย่างแรกของคุณฉันจะเขียนใหม่เป็น:

vector< vector< vector<int> > > A;
int i,j,k;
for(k=0;k<A.size();k++) for(i=0;i<A[k].size();i++) for(j=0;j<A[k][i].size();j++) {
    do_something_on_A(A[k][i][j]);
}

นี่เป็นการผลักดันเนื่องจากคุณกำลังเรียกใช้ฟังก์ชันในลูปด้านนอกซึ่งเทียบเท่ากับการใส่คำสั่งลงไป ฉันได้ลบพื้นที่สีขาวที่ไม่จำเป็นออกทั้งหมดแล้วและมันอาจจะผ่านได้

ตัวอย่างที่สองดีกว่ามาก:

double B[10][8][5];
int i,j,k;

for(k=0;k<10;k++) for(i=0;i<8;i++) for(j=0;j<5;j++) {
    do_something_on_B(B[k][i][j]);
}

นี่อาจเป็นรูปแบบช่องว่างที่แตกต่างจากที่คุณต้องการใช้ แต่ได้ผลลัพธ์ที่กะทัดรัดโดยที่ไม่ต้องการความรู้ใด ๆ นอกเหนือจาก C / C ++ (เช่นการประชุมมาโคร) และไม่ต้องใช้กลอุบายใด ๆ เช่นมาโคร

หากคุณต้องการมาโครจริงๆคุณสามารถก้าวไปอีกขั้นด้วยสิ่งต่อไปนี้:

#define FOR3(a,b,c,d,e,f,g,h,i) for(a;b;c) for(d;e;f) for(g;h;i)

ซึ่งจะเปลี่ยนตัวอย่างที่สองเป็น:

double B[10][8][5];
int i,j,k;

FOR3(k=0,k<10,k++,i=0,i<8,i++,j=0,j<5,j++) {
    do_something_on_B(B[k][i][j]);
}

และตัวอย่างแรกก็ราคาดีกว่าเช่นกัน:

vector< vector< vector<int> > > A;
int i,j,k;
FOR3(k=0,k<A.size(),k++,i=0,i<A[k].size(),i++,j=0,j<A[k][i].size(),j++) {
    do_something_on_A(A[k][i][j]);
}

หวังว่าคุณจะบอกได้ค่อนข้างง่ายว่างบไหนไปกับงบไหน นอกจากนี้ระวังจุลภาคตอนนี้คุณไม่สามารถใช้พวกเขาในประโยคเดียวของใด ๆ ของfors


1
ความสามารถในการอ่านของสิ่งเหล่านี้น่ากลัว ติดขัดมากกว่าหนึ่งforวงบนบรรทัดไม่ได้ทำให้มันอ่านได้มากขึ้นก็จะทำให้มันน้อย

0

นี่คือการใช้งาน C ++ 11 ที่จัดการทุกสิ่งที่ทำซ้ำได้ โซลูชันอื่น จำกัด ตัวเองเฉพาะคอนเทนเนอร์ที่มี::iteratortypedefs หรืออาร์เรย์: แต่ a for_eachเกี่ยวกับการวนซ้ำไม่ใช่คอนเทนเนอร์

ฉันยังแยก SFINAE ไปยังจุดเดียวในไฟล์ is_iterableลักษณะ การจัดส่ง (ระหว่างองค์ประกอบและการวนซ้ำ) ทำได้โดยการจัดส่งแท็กซึ่งฉันพบว่าเป็นวิธีแก้ปัญหาที่ชัดเจนกว่า

คอนเทนเนอร์และฟังก์ชันที่ใช้กับองค์ประกอบทั้งหมดได้รับการส่งต่ออย่างสมบูรณ์แบบทำให้ทั้งสองconstและไม่constสามารถเข้าถึงช่วงและ functors ได้

#include <utility>
#include <iterator>

ฟังก์ชันเทมเพลตที่ฉันกำลังใช้งาน อย่างอื่นสามารถเข้าไปในเนมสเปซรายละเอียด:

template<typename C, typename F>
void for_each_flat( C&& c, F&& f );

การจัดส่งแท็กนั้นสะอาดกว่า SFINAE มาก ทั้งสองนี้ใช้สำหรับวัตถุที่ทำซ้ำได้และวัตถุที่ทำซ้ำไม่ได้ตามลำดับ การทำซ้ำครั้งสุดท้ายของครั้งแรกอาจใช้การส่งต่อที่สมบูรณ์แบบ แต่ฉันขี้เกียจ:

template<typename C, typename F>
void for_each_flat_helper( C&& c, F&& f, std::true_type /*is_iterable*/ ) {
  for( auto&& x : std::forward<C>(c) )
    for_each_flat(std::forward<decltype(x)>(x), f);
}
template<typename D, typename F>
void for_each_flat_helper( D&& data, F&& f, std::false_type /*is_iterable*/ ) {
  std::forward<F>(f)(std::forward<D>(data));
}

is_iterableนี่คือต้นแบบบางอย่างที่จำเป็นเพื่อที่จะเขียน ฉันทำการค้นหาอาร์กิวเมนต์ขึ้นอยู่กับbeginและendในเนมสเปซรายละเอียด สิ่งนี้เลียนแบบสิ่งที่for( auto x : y )ลูปทำได้ดีพอสมควร:

namespace adl_aux {
  using std::begin; using std::end;
  template<typename C> decltype( begin( std::declval<C>() ) ) adl_begin(C&&);
  template<typename C> decltype( end( std::declval<C>() ) ) adl_end(C&&);
}
using adl_aux::adl_begin;
using adl_aux::adl_end;

TypeSinkเป็นประโยชน์ในการทดสอบว่ารหัสไม่ถูกต้อง คุณทำTypeSink< decltype(รหัส) >และหากถูกต้องแสดงออกเป็นcode voidหากรหัสไม่ถูกต้อง SFINAE จะเข้ามาและความเชี่ยวชาญจะถูกบล็อก:

template<typename> struct type_sink {typedef void type;};
template<typename T> using TypeSink = typename type_sink<T>::type;

template<typename T, typename=void>
struct is_iterable:std::false_type{};
template<typename T>
struct is_iterable<T, TypeSink< decltype( adl_begin( std::declval<T>() ) ) >>:std::true_type{};

ฉันทดสอบเพื่อbegin. การadl_endทดสอบสามารถทำได้

การใช้งานขั้นสุดท้ายfor_each_flatจบลงอย่างง่ายดายมาก:

template<typename C, typename F>
void for_each_flat( C&& c, F&& f ) {
  for_each_flat_helper( std::forward<C>(c), std::forward<F>(f), is_iterable<C>() );
}        

ตัวอย่างสด

นี่คือทางลงที่ด้านล่าง: อย่าลังเลที่จะรุกล้ำหาคำตอบด้านบนซึ่งเป็นของแข็ง ฉันแค่อยากได้เทคนิคที่ดีกว่านี้มาใช้!


-2

ประการแรกคุณไม่ควรใช้เวกเตอร์ของเวกเตอร์ของเวกเตอร์ เวกเตอร์แต่ละตัวได้รับการรับรองว่ามีหน่วยความจำที่ต่อเนื่องกัน แต่หน่วยความจำ "ส่วนกลาง" ของเวกเตอร์ของเวกเตอร์ไม่ใช่ (และอาจจะไม่เป็น) คุณควรใช้อาร์เรย์ประเภทไลบรารีมาตรฐานแทนอาร์เรย์แบบ C เช่นกัน

using std::array;

array<array<array<double, 5>, 8>, 10> B;
for (int k=0; k<10; k++)
    for (int i=0; i<8; i++)
        for (int j=0; j<5; j++)
            do_something_on_B(B[k][i][j]);

// or, if you really don't like that, at least do this:

for (int k=0; k<10; k++) {
    for (int i=0; i<8; i++) {
        for (int j=0; j<5; j++) {
            do_something_on_B(B[k][i][j]);
        }
    }
}

ยังดีกว่าคุณสามารถกำหนดคลาสเมทริกซ์ 3 มิติอย่างง่าย:

#include <stdexcept>
#include <array>

using std::size_t;

template <size_t M, size_t N, size_t P>
class matrix3d {
    static_assert(M > 0 && N > 0 && P > 0,
                  "Dimensions must be greater than 0.");
    std::array<std::array<std::array<double, P>, N>, M> contents;
public:
    double& at(size_t i, size_t j, size_t k)
    { 
        if (i >= M || j >= N || k >= P)
            throw out_of_range("Index out of range.");
        return contents[i][j][k];
    }
    double& operator(size_t i, size_t j, size_t k)
    {
        return contents[i][j][k];
    }
};

int main()
{
    matrix3d<10, 8, 5> B;
        for (int k=0; k<10; k++)
            for (int i=0; i<8; i++)
                for (int j=0; j<5; j++)
                    do_something_on_B(B(i,j,k));
    return 0;
}

คุณสามารถไปต่อได้และทำให้มันถูกต้องครบถ้วนเพิ่มการคูณเมทริกซ์ (เหมาะสมและเป็นองค์ประกอบอย่างชาญฉลาด) การคูณด้วยเวกเตอร์ ฯลฯ คุณสามารถสรุปเป็นประเภทต่างๆได้ (ฉันจะทำให้เป็นเทมเพลตถ้าคุณใช้คู่เป็นหลัก) .

คุณยังสามารถเพิ่มวัตถุพร็อกซีเพื่อให้คุณสามารถทำ B [i] หรือ B [i] [j] ได้ พวกเขาสามารถส่งคืนเวกเตอร์ (ในความหมายทางคณิตศาสตร์) และเมทริกซ์ที่เต็มไปด้วยคู่ & อาจเป็น?

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