Clean ways to write multiple 'for' loops

前端 未结 16 933
傲寒
傲寒 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:48

    The first thing is that you don't use such a data structure. If you need a three dimensional matrix, you define one:

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

    Or if you want to index using [][][], you need an operator[] which returns a proxy.

    Once you've done this, if you find that you constantly have to iterate as you've presented, you expose an iterator which will support it:

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

    Then you just write:

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

    (or just:

    for ( auto& elem: m ) {
    }
    

    if you have C++11.)

    And if you need the three indexes during such iterations, it's possible to create an iterator which exposes them:

    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);
            }
            //  ...
        };
    };
    
    0 讨论(0)
  • 2020-12-22 15:48

    Stick with the nested for loops!

    All the methods suggested here have disadvantages in terms of either readability or flexibility.

    What happens if you need to use the results of an inner loop for the processing in the outer loop? What happens if you need a value from the outer loop within your inner loop? Most of the "encapsulation" methods fail here.

    Trust me I have seen several attempts to "clean up" nested for loops and in the end it turns out that the nested loop is actually the cleanest and most flexible solution.

    0 讨论(0)
  • 2020-12-22 15:52

    Something like this can help:

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

    In order to make it N-ary we need some template magic. First of all we should create SFINAE structure to distinguish whether this value or container. The default implementation for values, and specialisations for arrays and each of the container types. How @Zeta notes, we can determine the standard containers by the nested iterator type (ideally we should check whether the type can be used with range-base for or not).

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

    Implementation of for_each is straightforward. The default function will call 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);
     }
    

    And the specialisation will call itself recursively:

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

    And 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,
     }
    

    Also this will not work for pointers (arrays allocated in heap).

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

    UPDATE: I know, that you asked for it, but you'd better not use that :)

    0 讨论(0)
  • 2020-12-22 15:52

    You could generate indices in one loop like this (A, B, C are dimensions):

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

    Here is a C++11 implementation that handles everything iterable. Other solutions restrict themselves to containers with ::iterator typedefs or arrays: but a for_each is about iteration, not being a container.

    I also isolate the SFINAE to a single spot in the is_iterable trait. The dispatching (between elements and iterables) is done via tag dispatching, which I find is a clearer solution.

    The containers and the functions applied to elements are all perfect forwarded, allowing both const and non-const access to the ranges and functors.

    #include <utility>
    #include <iterator>
    

    The template function I am implementing. Everything else could go into a details namespace:

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

    Tag dispatching is much cleaner than SFINAE. These two are used for iterable objects and non iterable objects respectively. The last iteration of the first could use perfect forwarding, but I am lazy:

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

    This is some boilerplate required in order to write is_iterable. I do argument dependent lookup on begin and end in a detail namespace. This emulates what a for( auto x : y ) loop does reasonably well:

    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;
    

    The TypeSink is useful to test if code is valid. You do TypeSink< decltype( code ) > and if the code is valid, the expression is void. If the code is not valid, SFINAE kicks in and the specialization is blocked:

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

    I only test for begin. An adl_end test could also be done.

    The final implementation of for_each_flat ends up being extremely simple:

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

    Live example

    This is way down at the bottom: feel free to poach for the top answers, which are solid. I just wanted a few better techniques to be used!

    0 讨论(0)
提交回复
热议问题