Các cách rõ ràng để viết nhiều vòng lặp 'for'


98

Đối với một mảng có nhiều kích thước, chúng ta thường cần viết một forvòng lặp cho mỗi kích thước của nó. Ví dụ:

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

Bạn for-for-forthường xuyên thấy loại vòng lặp này trong mã của chúng tôi. Làm cách nào để sử dụng macro để xác định các for-for-forvòng lặp để tôi không cần phải viết lại loại mã này mỗi lần? Có cách nào tốt hơn để làm điều này?


62
Câu trả lời rõ ràng là bạn không. Bạn không tạo ngôn ngữ mới bằng macro (hoặc bất kỳ kỹ thuật nào khác); người đến sau bạn sẽ không thể đọc mã.
James Kanze

17
Khi bạn có một vector của một vector của một vector, đó là một dấu hiệu của thiết kế tồi.
Maroun

5
@Nim: Bạn có thể làm điều đó với 1 mảng phẳng (không chắc là tốt hơn).
Jarod42

16
Tôi nghĩ rằng bạn sẽ không muốn để ẩn tiềm năng O(n) = n^3mã ...
poy

36
@ TC1: Và sau đó tôi sẽ cảm thấy khó đọc hơn. Tất cả là một câu hỏi về sở thích cá nhân và nó thực sự không giúp được gì cho câu hỏi ở đây.
vào

Câu trả lời:


281

Điều đầu tiên là bạn không sử dụng cấu trúc dữ liệu như vậy. Nếu bạn cần một ma trận ba chiều, bạn xác định một:

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

Hoặc nếu bạn muốn lập chỉ mục bằng cách sử dụng [][][], bạn cần một mã operator[] trả về proxy.

Khi bạn đã thực hiện xong việc này, nếu bạn thấy rằng bạn liên tục phải lặp lại như bạn đã trình bày, bạn sẽ hiển thị một trình lặp sẽ hỗ trợ nó:

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

Sau đó, bạn chỉ cần viết:

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

(hoặc chỉ:

for ( auto& elem: m ) {
}

nếu bạn có C ++ 11.)

Và nếu bạn cần ba chỉ mục trong các lần lặp lại như vậy, bạn có thể tạo một trình lặp để hiển thị chúng:

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
Câu trả lời này nên được nhiều người ủng hộ hơn vì nó là câu trả lời duy nhất giải quyết nguồn gốc thực sự của vấn đề.
vào

5
nó có thể là câu trả lời corret nhưng tôi không đồng ý nó là một câu trả lời hay. rất nhiều mã mẫu khó hiểu với thời gian biên dịch chậm có lẽ x10 lần và có thể là mã gỡ lỗi chậm x10 (có thể nhiều hơn). Đối với tôi mã chắc chắn là ban đầu là cách rõ ràng hơn đối với tôi ...
Gorkem

10
@beehorf ... và cũng chậm hơn nhiều. Bởi vì mảng nhiều chiều trong C và C ++ thực sự là mảng lồng nhau theo nghĩa là các kích thước bên ngoài lưu trữ các con trỏ đến các mảng lồng nhau. Các mảng lồng nhau này sau đó được phân tán tùy ý xung quanh trong bộ nhớ, đánh bại mọi hoạt động tìm nạp trước và lưu vào bộ nhớ đệm. Tôi biết những ví dụ mà ai đó đã viết mã sử dụng vector<vector<vector<double> > >để biểu diễn trường 3 chiều. Viết lại mã tương đương với giải pháp trên dẫn đến một sự tăng tốc của 10
Michael hoang dã

5
@beehorf Bạn thấy mã mẫu nào ở đâu? (Trong thực tế, Matrix3Dcó lẽ nên là một mẫu, nhưng đó là một mẫu rất đơn giản.) Và bạn chỉ phải gỡ lỗi Matrix3D, không phải bất cứ khi nào bạn cần ma trận 3D, vì vậy bạn tiết kiệm rất nhiều thời gian trong việc gỡ lỗi. Đối với sự rõ ràng: làm thế nào là std::vector<std::vector<std::vector<int>>>rõ ràng hơn Matrix3D? Chưa kể điều đó Matrix3Dthực thi thực tế là bạn có một ma trận, trong khi các vectơ lồng nhau có thể bị xáo trộn, và điều trên có lẽ nhanh hơn đáng kể.
James Kanze

10
@MichaelWild Nhưng tất nhiên, lợi thế thực sự của cách tiếp cận của tôi là bạn có thể thay đổi cách biểu diễn, tùy thuộc vào những gì nhanh hơn trong môi trường của bạn mà không cần phải sửa đổi bất kỳ mã máy khách nào. Chìa khóa để có hiệu suất tốt là đóng gói thích hợp, để bạn có thể thực hiện các thay đổi mà trình biên dịch cho biết bạn cần mà không cần phải viết lại toàn bộ ứng dụng.
James Kanze

44

Sử dụng macro để ẩn các forvòng lặp có thể rất khó hiểu, chỉ để lưu một vài ký tự. Thay vào đó, tôi sẽ sử dụng vòng lặp range-for :

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

Tất nhiên, bạn có thể thay thế auto&bằng const auto&nếu bạn không sửa đổi dữ liệu.


3
Giả sử OP có thể sử dụng C ++ 11.
Jarod42

1
@herohuyongtao Trong trường hợp trình lặp. Điều này có thể khó hiểu hơn ở đây, nhưng có những trường hợp bạn muốn ba intbiến.
James Kanze

1
Và điều đó có nên không do_something_on_A(*j)?
James Kanze

1
@Jefffrey Ah, vâng. Một lý do khác để đánh vần kiểu này. (Tôi đoán việc sử dụng autocho kicó thể hợp lý ngoại trừ việc nó vẫn giải quyết vấn đề ở cấp sai; vấn đề thực sự là anh ấy sử dụng các vectơ lồng nhau..)
James Kanze

2
@Dhara klà một vectơ toàn bộ của vectơ (cũng là một tham chiếu đến nó), không phải là một chỉ số.
Yakk - Adam Nevraumont

21

Một cái gì đó như thế này có thể giúp:

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

Để biến nó thành N-ary, chúng ta cần một số phép mẫu. Trước hết chúng ta nên tạo cấu trúc SFINAE để phân biệt giá trị này hay vùng chứa. Việc triển khai mặc định cho các giá trị và chuyên môn cho mảng và mỗi loại vùng chứa. Cách @Zeta lưu ý, chúng ta có thể xác định các vùng chứa tiêu chuẩn theo iteratorkiểu lồng nhau (lý tưởng là chúng ta nên kiểm tra xem loại có thể được sử dụng với range-base forhay không).

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

Thực hiện for_eachlà đơn giản. Hàm mặc định sẽ gọi 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);
 }

Và chuyên ngành sẽ gọi chính nó một cách đệ quy:

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

Và 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,
 }

Ngoài ra, điều này sẽ không hoạt động đối với con trỏ (các mảng được phân bổ trong heap).


@herohuyongtao với các ràng buộc, chúng tôi có thể triển khai hai chuyên môn hóa cho Containervà cho những người khác.
fasked

1
@herohuyongtao Tôi đã làm một ví dụ về K-ary foreach.
phát hiện

1
@fasked: Sử dụng is_container : has_iterator<T>::valuetừ câu trả lời của tôi và bạn không cần phải viết chuyên môn hóa cho mọi loại, vì mọi vùng chứa phải có một iteratortypedef. Hãy hoàn toàn sử dụng bất cứ thứ gì từ câu trả lời của tôi, của bạn đã tốt hơn rồi.
Zeta

@Zeta +1 cho điều này. Cũng như tôi đã đề cập Containerkhái niệm sẽ giúp ích.
phát hiện

::iteratorkhông phải là một phạm vi có thể lặp lại. int x[2][3][4]là hoàn toàn có thể lặp lại, vì struct foo { int x[3]; int* begin() { return x; } int* end() { return x+3; } }; tôi không chắc T[]chuyên môn hóa phải làm gì?
Yakk - Adam Nevraumont

17

Hầu hết các câu trả lời chỉ đơn giản là chứng minh cách C ++ có thể bị biến thành các phần mở rộng cú pháp khó hiểu, IMHO.

Bằng cách xác định bất kỳ mẫu hoặc macro nào, bạn chỉ buộc các lập trình viên khác phải hiểu các bit của mã bị xáo trộn được thiết kế để ẩn các bit khác của mã bị xáo trộn.
Bạn sẽ buộc mọi người đọc mã của bạn phải có chuyên môn về khuôn mẫu, chỉ để tránh làm công việc xác định các đối tượng với ngữ nghĩa rõ ràng.

Nếu bạn quyết định sử dụng dữ liệu thô như mảng 3 chiều, chỉ cần sống với nó, hoặc nếu không hãy xác định một lớp mang lại một số ý nghĩa dễ hiểu cho dữ liệu của bạn.

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

chỉ phù hợp với định nghĩa khó hiểu về một vectơ của vectơ của vectơ int mà không có ngữ nghĩa rõ ràng.


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

CẬP NHẬT: Tôi biết, bạn đã yêu cầu nó, nhưng bạn không nên sử dụng nó :)


5
Tôi biết đó là những gì OP yêu cầu, nhưng nghiêm túc mà nói ... Điều này giống như một ví dụ tuyệt vời về sự xáo trộn. Giả sử TRIPLE_FORđã được xác định trong một số tiêu đề, tôi sẽ nghĩ gì khi nhìn thấy `TRIPLE_FOR ở đây.
James Kanze

2
Vâng, tôi đoán, bạn nói đúng :) Tôi nghĩ, tôi sẽ để nó ở đây chỉ như một ví dụ mà điều này có thể được thực hiện bằng cách sử dụng macro, nhưng thêm một lưu ý rằng tốt hơn là không nên làm như vậy :) Tôi vừa mới ngủ dậy và quyết định sử dụng câu hỏi này như một sự khởi động nhỏ cho tâm trí.
FreeNickname

5

Một ý tưởng là viết một lớp vùng chứa giả có thể lặp lại "chứa" tập hợp tất cả các bộ giá trị đa chỉ mục mà bạn sẽ lập chỉ mục. Không triển khai ở đây vì sẽ mất quá nhiều thời gian nhưng ý tưởng là bạn sẽ có thể viết ...

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

câu trả lời tốt nhất ở đây imo.
davidhigh

4

Tôi thấy nhiều câu trả lời ở đây hoạt động đệ quy, phát hiện xem đầu vào có phải là vùng chứa hay không. Thay vào đó, tại sao không phát hiện xem lớp hiện tại có phải là cùng loại với hàm không? Nó đơn giản hơn nhiều và cho phép nhiều chức năng mạnh mẽ hơn:

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

Tuy nhiên, điều này (rõ ràng) cho chúng ta những lỗi không rõ ràng. Vì vậy, chúng tôi sử dụng SFINAE để phát hiện xem đầu vào hiện tại có phù hợp với chức năng hay không

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

Điều này hiện xử lý các vùng chứa một cách chính xác, nhưng trình biên dịch vẫn coi điều này là không rõ ràng đối với các kiểu input_type có thể được truyền cho hàm. Vì vậy, chúng tôi sử dụng một thủ thuật C ++ 03 tiêu chuẩn để làm cho nó thích hàm đầu tiên hơn hàm thứ hai, đồng thời chuyển số 0 và làm cho hàm chúng tôi thích chấp nhận và int hơn, và hàm còn lại lấy ...

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

Đó là nó. Sáu dòng mã tương đối đơn giản và bạn có thể lặp qua các giá trị, hàng hoặc bất kỳ đơn vị con nào khác, không giống như tất cả các câu trả lời khác.

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

Bằng chứng về việc biên dịch và thực hiện ở đâyở đây

Nếu bạn muốn một cú pháp thuận tiện hơn trong C ++ 11, bạn có thể thêm macro. (Sau đây là chưa được kiểm tra)

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

Tôi báo trước câu trả lời này với câu lệnh sau: điều này sẽ chỉ hoạt động nếu bạn đang hoạt động trên một mảng thực - nó sẽ không hoạt động đối với ví dụ của bạn đang sử dụng std::vector.

Nếu bạn đang thực hiện cùng một thao tác trên mọi phần tử của mảng nhiều chiều, mà không quan tâm đến vị trí của từng mục, thì bạn có thể tận dụng lợi thế của thực tế là các mảng được đặt ở các vị trí bộ nhớ liền kề và coi toàn bộ như một mảng một chiều lớn. Ví dụ: nếu chúng tôi muốn nhân mọi phần tử với 2,0 trong ví dụ thứ hai của bạn:

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

Lưu ý rằng sử dụng phương pháp trên cũng cho phép sử dụng một số kỹ thuật C ++ "thích hợp":

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

Nói chung tôi không khuyên cách tiếp cận này (thích một cái gì đó giống như câu trả lời của Jefffrey), vì nó dựa vào việc xác định kích thước cho các mảng của bạn, nhưng trong một số trường hợp, nó có thể hữu ích.


Điều này là bất hợp pháp: stackoverflow.com/questions/6015080/…
ecatmur

@ecatmur: Thật thú vị - Tôi chỉ mới bắt đầu làm việc, vì vậy tôi sẽ kiểm tra điều này và cập nhật / xóa câu trả lời cho phù hợp. Cảm ơn.
icabod

@ecatmur: Tôi đã xem xét tiêu chuẩn C ++ 11 (phần 8.3.4) và những gì tôi đã viết sẽ hoạt động và không có vẻ bất hợp pháp (đối với tôi). Liên kết bạn cung cấp liên quan đến việc truy cập các thành viên bên ngoài kích thước mảng đã xác định. Mặc dù đúng là tôi nhận được địa chỉ của mảng vừa qua, nhưng nó không truy cập vào dữ liệu - điều này là để cung cấp một "kết thúc", giống như cách bạn có thể sử dụng con trỏ làm trình vòng lặp, với "kết thúc" là một quá khứ phần tử cuối cùng.
icabod

Bạn đang truy cập hiệu quả B[0][0][i]cho i >= 3; điều này không được phép vì nó đang truy cập bên ngoài mảng (bên trong).
ecatmur

1
Một cách rõ ràng hơn IMO trong việc chỉ định kết thúc NẾU bạn phải làm điều này là end = start + (xSize * ySize * zSize)
noggin182

2

Tôi hơi sốc khi không ai đề xuất một số vòng lặp dựa trên phép thuật số học để thực hiện công việc này. C. Wang đang tìm kiếm một giải pháp không có vòng lặp lồng nhau , tôi sẽ đề xuất một giải pháp :

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

Chà, cách tiếp cận này không thanh lịch và linh hoạt, vì vậy chúng tôi có thể đóng gói tất cả quy trình vào một hàm mẫu:

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

Hàm mẫu này cũng có thể được thể hiện dưới dạng các vòng lặp lồng nhau:

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

Và có thể được sử dụng để cung cấp một mảng 3D có kích thước tùy ý cộng với tên hàm, cho phép việc khấu trừ tham số thực hiện công việc khó khăn khi đếm kích thước của mỗi chiều:

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

Hướng tới chung chung hơn

Nhưng một lần nữa, nó thiếu tính linh hoạt vì nó chỉ hoạt động cho các mảng 3D, nhưng sử dụng SFINAE, chúng ta có thể thực hiện công việc cho các mảng có kích thước tùy ý, trước tiên chúng ta cần một hàm mẫu lặp lại các mảng có thứ hạng 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);
    }
}

Và một cái khác lặp lại các mảng có thứ hạng bất kỳ, thực hiện đệ quy:

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

Điều này cho phép chúng tôi lặp lại tất cả các phần tử trong tất cả các kích thước của một mảng có kích thước tùy ý có kích thước tùy ý.


Làm việc với std::vector

Đối với nhiều vectơ lồng nhau, giải pháp sẽ lắp lại một mảng có kích thước tùy ý có kích thước tùy ý, nhưng không có SFINAE: Đầu tiên, chúng ta sẽ cần một hàm mẫu lặp lại std::vectors và gọi hàm mong muốn:

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

Và một hàm mẫu khác lặp lại bất kỳ loại vectơ nào của vectơ và gọi chính nó:

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

Bất kể mức độ lồng nhau, iterate_allsẽ gọi phiên bản vectơ-of-vectơ trừ khi phiên bản vectơ-of-giá trị là một kết quả phù hợp hơn, do đó kết thúc đệ quy.

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

Tôi nghĩ rằng phần thân hàm khá đơn giản và dễ hiểu ... Tôi tự hỏi liệu trình biên dịch có thể giải nén các vòng lặp này hay không (tôi gần như chắc chắn rằng hầu hết các trình biên dịch có thể giải nén ví dụ đầu tiên).

Xem demo trực tiếp tại đây .

Hy vọng nó giúp.


1

Sử dụng một cái gì đó dọc theo những dòng này (mã giả của nó, nhưng ý tưởng vẫn giữ nguyên). Bạn trích xuất mẫu để lặp lại một lần và áp dụng một chức năng khác nhau mỗi lần.

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

Gắn bó với các vòng lặp for lồng nhau!

Tất cả các phương pháp được đề xuất ở đây đều có nhược điểm về khả năng đọc hoặc tính linh hoạt.

Điều gì xảy ra nếu bạn cần sử dụng kết quả của một vòng lặp bên trong cho quá trình xử lý ở vòng lặp bên ngoài? Điều gì xảy ra nếu bạn cần một giá trị từ vòng lặp bên ngoài trong vòng lặp bên trong của bạn? Hầu hết các phương pháp "đóng gói" không thành công ở đây.

Tin tưởng tôi Tôi đã thấy một số nỗ lực để "dọn dẹp" các vòng lặp for lồng nhau và cuối cùng hóa ra rằng vòng lặp lồng nhau thực sự là giải pháp sạch sẽ và linh hoạt nhất.


0

Một kỹ thuật tôi đã sử dụng là các mẫu. Ví dụ:

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
}

Sau đó, bạn chỉ cần gọi do_something_on_A(A)bằng mã chính của mình. Hàm mẫu được tạo một lần cho mỗi thứ nguyên, lần đầu tiên với T = std::vector<std::vector<int>>, lần thứ hai với với T = std::vector<int>.

Bạn có thể làm cho điều này chung chung hơn bằng cách sử dụng std::function(hoặc các đối tượng giống như hàm trong C ++ 03) làm đối số thứ hai nếu bạn muốn:

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

Sau đó gọi nó như:

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

Điều này hoạt động ngay cả khi các hàm có cùng một chữ ký vì hàm đầu tiên phù hợp hơn với bất kỳ thứ gì có std::vectortrong kiểu.


0

Bạn có thể tạo các chỉ số trong một vòng lặp như thế này (A, B, C là các thứ nguyên):

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

Tôi đồng ý với bạn, nó được thiết kế đặc biệt cho 3 kích thước;)
Janek

1
Chưa kể là nó cực kỳ chậm!
noggin182

@ noggin182: câu hỏi không phải về tốc độ mà là về việc tránh các vòng lặp for lồng nhau; bên cạnh đó, có những bộ phận không cần thiết đó, i / (B * C) có thể được thay thế bằng một
Janek

Được rồi, đây là một cách thay thế, có lẽ hiệu quả hơn (javascript): for (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); }
janek

0

Một điều bạn có thể muốn thử nếu bạn chỉ có các câu lệnh trong vòng lặp bên trong nhất - và mối quan tâm của bạn nhiều hơn về bản chất quá dài dòng của mã - là sử dụng một lược đồ khoảng trắng khác. Điều này sẽ chỉ hoạt động nếu bạn có thể xác định các vòng lặp for của mình đủ nhỏ gọn để tất cả chúng nằm gọn trên một dòng.

Đối với ví dụ đầu tiên của bạn, tôi sẽ viết lại nó thành:

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

Điều này gần như đang đẩy nó bởi vì bạn đang gọi các hàm trong các vòng ngoài, tương đương với việc đặt các câu lệnh trong chúng. Tôi đã xóa tất cả các khoảng trắng không cần thiết và nó có thể được thực hiện.

Ví dụ thứ hai tốt hơn nhiều:

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

Đây có thể là quy ước khoảng trắng khác với quy ước mà bạn muốn sử dụng, nhưng nó đạt được kết quả nhỏ gọn mà vẫn không yêu cầu bất kỳ kiến ​​thức nào ngoài C / C ++ (chẳng hạn như quy ước macro) và không yêu cầu bất kỳ thủ thuật nào như macro.

Nếu bạn thực sự muốn có một macro, thì bạn có thể thực hiện bước này xa hơn với những thứ như:

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

sẽ thay đổi ví dụ thứ hai thành:

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

và ví dụ đầu tiên cũng có giá tốt hơn:

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

Hy vọng rằng bạn có thể phân biệt khá dễ dàng câu lệnh nào đi với câu lệnh nào. Ngoài ra, hãy cẩn thận với dấu phẩy, bây giờ bạn không thể sử dụng chúng trong một mệnh đề duy nhất của bất kỳ fors.


1
Khả năng đọc của những thứ này là kinh khủng. Kẹt nhiều hơn một forvòng lặp vào một dòng không làm cho nó dễ đọc hơn mà làm cho nó ít hơn .

0

Đây là một triển khai C ++ 11 xử lý mọi thứ có thể lặp lại. Các giải pháp khác tự giới hạn các vùng chứa có ::iteratortypedefs hoặc mảng: nhưng a for_eachlà về sự lặp lại, không phải là vùng chứa.

Tôi cũng cô lập SFINAE thành một điểm duy nhất trong is_iterableđặc điểm. Việc điều phối (giữa các phần tử và các tệp lặp) được thực hiện thông qua điều phối thẻ, mà tôi thấy là một giải pháp rõ ràng hơn.

Các vùng chứa và các chức năng được áp dụng cho các phần tử đều được chuyển tiếp hoàn hảo, cho phép cả constvà không consttruy cập vào phạm vi và chức năng.

#include <utility>
#include <iterator>

Chức năng mẫu mà tôi đang triển khai. Mọi thứ khác có thể đi vào không gian tên chi tiết:

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

Điều phối thẻ sạch hơn nhiều so với SFINAE. Hai cái này được sử dụng cho các đối tượng có thể lặp lại và các đối tượng không thể lặp lại tương ứng. Lần lặp cuối cùng của lần đầu tiên có thể sử dụng chuyển tiếp hoàn hảo, nhưng tôi lười:

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

Đây là một số bản viết sẵn cần thiết để viết is_iterable. Tôi thực hiện tra cứu đối số phụ thuộc vào beginendtrong một không gian tên chi tiết. Điều này mô phỏng những gì một for( auto x : y )vòng lặp hoạt động khá tốt:

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;

Điều TypeSinknày rất hữu ích để kiểm tra xem mã có hợp lệ hay không. Bạn TypeSink< decltype(viết mã ) >và nếu codegiá trị hợp lệ, thì biểu thức là void. Nếu mã không hợp lệ, SFINAE sẽ bắt đầu và chuyên môn bị chặn:

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

Tôi chỉ kiểm tra cho begin. Một adl_endthử nghiệm cũng có thể được thực hiện.

Việc thực hiện cuối cùng của for_each_flatkết thúc cực kỳ đơn giản:

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

Ví dụ trực tiếp

Đây là cách ở phía dưới: hãy thoải mái tìm kiếm các câu trả lời trên cùng, những câu trả lời chắc chắn. Tôi chỉ muốn một vài kỹ thuật tốt hơn được sử dụng!


-2

Thứ nhất, bạn không nên sử dụng vectơ vectơ của vectơ. Mỗi vectơ được đảm bảo có bộ nhớ liền kề, nhưng bộ nhớ "chung" của một vectơ của vectơ thì không (và có thể sẽ không có). Bạn nên sử dụng mảng kiểu thư viện chuẩn thay vì mảng kiểu 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]);
        }
    }
}

Tuy nhiên, tốt hơn hết, bạn có thể xác định một lớp ma trận 3D đơn giản:

#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ạn có thể đi xa hơn và làm cho nó hoàn toàn chính xác, thêm phép nhân ma trận (thích hợp và theo phần tử), phép nhân với vectơ, v.v. Bạn thậm chí có thể tổng quát hóa nó thành các kiểu khác nhau (Tôi sẽ làm cho nó làm mẫu nếu bạn chủ yếu sử dụng nhân đôi) .

Bạn cũng có thể thêm các đối tượng proxy để bạn có thể làm B [i] hoặc B [i] [j]. Họ có thể trả về vectơ (theo nghĩa toán học) và ma trận đầy kép &, có khả năng không?

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.