Clean ways to write multiple 'for' loops

前端 未结 16 934
傲寒
傲寒 2020-12-22 15:22

For an array with multiple dimensions, we usually need to write a for loop for each of its dimensions. For example:

vector< vector< vector         


        
相关标签:
16条回答
  • 2020-12-22 15:56

    I see many answers here that work recursively, detecting if the input is a container or not. Instead, why not detect if the current layer is the same type as the function takes? It's far simpler, and allows for more powerful functions:

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

    However, this (obviously) gives us ambiguity errors. So we use SFINAE to detect if the current input fits in the function or not

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

    This now handles the containers correctly, but the compiler still considers this ambiguous for input_types that can be passed to the function. So we use a standard C++03 trick to make it prefer the first function over the second, of also passing a zero, and making the one we prefer accept and int, and the other takes ...

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

    That's it. Six, relatively simple lines of code, and you can iterate over values, rows, or any other sub-unit, unlike all of the other answers.

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

    Proof of compilation and execution here and here

    If you wanted a more convenient syntax in C++11, you could add a macro. (Following is untested)

    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 << ", ";
         }
    }
    
    0 讨论(0)
  • 2020-12-22 15:56

    Firstly, you shouldn't use a vector of vectors of vectors. Each vector is guaranteed to have contiguous memory, but the "global" memory of a vector of vectors isn't (and probably won't be). You should use the standard library type array instead of C-style arrays as well.

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

    Better yet though, you could define a simple 3D matrix class:

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

    You could go further and make it fully const-correct, add matrix multiplication (proper and element-wise), multiplication by vectors, etc. You could even generalise it to different types (I'd make it template if you mainly use doubles).

    You could also add proxy objects so you can do B[i] or B[i][j]. They could return vectors (in the mathematical sense) and matrices full of double&, potentially?

    0 讨论(0)
  • 2020-12-22 16:00

    One thing you may want to try if you only have statements in the inner-most loop - and your concern is more about the overly verbose nature of the code - is to use a different whitespace scheme. This will only work if you can state your for loops compactly enough so that they all fit on one line.

    For your first example, I would rewrite it as:

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

    This is kinda pushing it because you are calling functions in the outer loops which is equivalent to putting statements in them. I have removed all unnecessary white-space and it may be passible.

    The second example is much better:

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

    This may be different whitespace convention than you like to use, but it achieves a compact result that nonetheless does not require any knowledge beyond C/C++ (such as macro conventions) and does not require any trickery like macros.

    If you really want a macro, you could then take this a step further with something like:

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

    which would change the second example to:

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

    and the first example fares better too:

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

    Hopefully you can tell fairly easily which statements go with which for statements. Also, beware the commas, now you can't use them in a single clause of any of the fors.

    0 讨论(0)
  • 2020-12-22 16:03

    One idea is to write an iterable pseudo-container class that "contains" the set of all multi-index tuples you'll index over. No implementation here because it'll take too long but the idea is that you should be able to write...

    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.
    }
    
    0 讨论(0)
提交回复
热议问题