清除编写多个'for'循环的方法

时间:2014-01-08 11:27:06

标签: c++ for-loop

对于具有多个维度的数组,我们通常需要为每个维度编写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循环,这样我每次都不需要重写这种代码?有更好的方法吗?

16 个答案:

答案 0 :(得分:280)

首先,您不要使用这样的数据结构。如果 你需要一个三维矩阵,你定义一个:

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

答案 1 :(得分:43)

使用宏隐藏for循环可能会让人感到困惑,只是为了节省很少的字符。我改用range-for循环:

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

如果您实际上没有修改数据,那么您可以将auto&替换为const auto&

答案 2 :(得分: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类型确定标准容器(理想情况下,我们应该检查该类型是否可以与范围基础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);
 }

瞧:

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

这也不适用于指针(在堆中分配的数组)。

答案 3 :(得分:17)

大多数答案只是演示了如何将C ++扭曲成难以理解的句法扩展,恕我直言。

通过定义任何模板或宏,您只需强制其他程序员理解一些混淆代码,这些代码旨在隐藏其他混淆代码。
您将强制每个读取您代码的人都具有模板专业知识,以避免您使用明确的语义来定义对象。

如果您决定使用三维数组等原始数据,只需使用它,或者定义一个为您的数据提供一些可理解含义的类。

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

与int矢量向量的神秘定义一致,没有明确的语义。

答案 4 :(得分: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 :(得分:5)

一个想法是编写一个可迭代的伪容器类,它“包含”你要索引的所有多索引元组的集合。这里没有实现,因为它需要太长时间,但想法是你应该能够写...

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

答案 6 :(得分: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);

通常不会建议这种方法(更像是Jefffrey的回答),因为它依赖于为数组定义的大小,但在某些情况下它可能很有用。

答案 7 :(得分:3)

我在这里看到许多以递归方式工作的答案,检测输入是否是容器。相反,为什么不检测当前图层是否与函数采用的类型相同?它更简单,并允许更强大的功能:

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

编译和执行证明herehere

如果你想在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 << ", ";
     }
}

答案 8 :(得分: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);
            }
        }
    }
}

可以使用提供任意大小的3D数组加上函数名称,让参数推导做了计算每个维度大小的艰苦工作:

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

走向更通用的

但又一次,它缺乏灵活性,因为它只适用于3D阵列,但使用SFINAE我们可以为任意维度的数组做工作,首先我们需要一个迭代{{{ 3}} 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;
}

我认为函数体非常简单直接......我想知道编译器是否可以展开这个循环(我几乎可以肯定大多数编译器都可以展开第一个例子)。

请参阅rank

希望它有所帮助。

答案 9 :(得分: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)

答案 10 :(得分:1)

坚持使用嵌套的for循环!

这里提出的所有方法在可读性或灵活性方面都有缺点。

如果需要在外循环中使用内循环的结果进行处理,会发生什么?如果你需要内循环中外循环的值,会发生什么?大多数“封装”方法都失败了。

相信我,我已经看到了几次“清理”嵌套for循环的尝试,最后证明嵌套循环实际上是最干净,最灵活的解决方案。

答案 11 :(得分:0)

我使用的一种技术是模板。 E.g:

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的任何内容。

答案 12 :(得分:0)

这是一个C ++ 11实现,可以处理迭代的所有内容。其他解决方案将自己局限于具有::iterator typedef或数组的容器:但for_each是关于迭代的,而不是容器。

我还将SFINAE分离到is_iterable特征中的单个点。调度(元素和迭代之间)通过标签调度完成,我发现这是一个更清晰的解决方案。

应用于元素的容器和函数都是完美转发的,允许const和非const访问范围和仿函数。

#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所需的一些样板文件。我在详细命名空间中对beginend进行参数依赖查找。这模拟了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>() );
}        

Live example

这是在底部的方式:随意挖掘最佳答案,这是坚实的。我只是想要一些更好的技术来使用!

答案 13 :(得分: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;
}

答案 14 :(得分:0)

如果你只在最内层的循环中有语句,那么你可能想要尝试的一件事 - 你关心的更多是关于代码的过于冗长的性质 - 就是使用不同的空格方案。只有当你能够足够紧凑地陈述你的for循环以便它们全部适合一行时,这才有效。

对于您的第一个示例,我将其重写为:

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

希望你可以很容易地告诉哪些陈述与哪些陈述有关。另外,请注意逗号,现在您不能在任何for的单个子句中使用它们。

答案 15 :(得分:-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]);
        }
    }
}

但更好的是,您可以定义一个简单的3D矩阵类:

#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]。它们可以返回向量(在数学意义上)和充满双重和可能的矩阵