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

C. *_*ang 97 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]);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

for-for-for经常在我们的代码中看到这种循环.如何使用宏来定义for-for-for循环,这样我每次都不需要重写这种代码?有一个更好的方法吗?

Jam*_*nze 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 ];
    }
};
Run Code Online (Sandbox Code Playgroud)

或者,如果要使用索引[][][],则需要使用operator[] 返回代理的方法.

一旦你完成了这个,如果你发现你不得不像你所呈现的那样进行迭代,你会暴露一个支持它的迭代器:

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

那你就写:

for ( Matrix3D::iterator iter = m.begin(); iter != m.end(); ++ iter ) {
    //  ...
}
Run Code Online (Sandbox Code Playgroud)

(要不就:

for ( auto& elem: m ) {
}
Run Code Online (Sandbox Code Playgroud)

如果你有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);
        }
        //  ...
    };
};
Run Code Online (Sandbox Code Playgroud)

  • 这个答案应该更加受欢迎,因为它是唯一一个处理问题实际来源的答案. (21认同)
  • @beehorf ......还有很多,慢得多.因为C和C++中的多维数组实际上是嵌套数组,因为外部维度存储指向嵌套数组的指针.然后,这些嵌套数组随意分散在内存中,有效地消除了任何预取和缓存.我知道有些人使用`vector <vector <vector <double>>>来编写代码以表示三维字段的示例.重写相当于上述解决方案的代码导致加速为10. (10认同)
  • @MichaelWild但是,当然,我的方法的真正优势在于您可以根据环境中的更快速度更改表示,而无需修改任何客户端代码.良好性能的关键是适当的封装,以便您可以在不必重写整个应用程序的情况下进行分析器所需的更改. (10认同)
  • 它可能是正确答案,但我不同意这是一个很好的答案.许多神秘的模板代码,编译时间可能是x10倍,可能是x10慢调试(可能更多)代码.对我来说,最初的原始代码对我来说更清楚...... (5认同)
  • @beehorf你在哪里看到任何模板代码?(实际上,`Matrix3D`可能应该是一个模板,但它是一个非常简单的模板.)而且你只需要调试`Matrix3D`,而不是每次需要3D矩阵时,所以你在调试中节省了大量的时间.至于清晰度:`std :: vector <std :: vector <std :: vector <int >>>`比`Matrix3D更清晰?更不用说`Matrix3D`强制执行一个事实,即你有一个矩阵,而嵌套的矢量可能是不规则的,而且上面的速度可能要快得多. (5认同)

Sho*_*hoe 43

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

for (auto& k : A)
    for (auto& i : k)
        for (auto& j : i)
            do_something_on_A(j);
Run Code Online (Sandbox Code Playgroud)

当然auto&,const auto&如果您实际上没有修改数据,则可以替换.

  • `auto`很棒! (4认同)
  • 假设OP可以使用C++ 11. (3认同)
  • @Dhara`k`是矢量的整个向量(对它的引用),而不是索引. (2认同)

fas*_*ked 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; });
 }
Run Code Online (Sandbox Code Playgroud)

为了使它成为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 {};
Run Code Online (Sandbox Code Playgroud)

实施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);
 }
Run Code Online (Sandbox Code Playgroud)

专业化将递归地调用自己:

 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);
 }
Run Code Online (Sandbox Code Playgroud)

瞧:

 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,
 }
Run Code Online (Sandbox Code Playgroud)

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


kur*_*eko 17

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

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

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

for (auto& k : A)
for (auto& i : k)
for (auto& current_A : i)
    do_something_on_A(current_A);
Run Code Online (Sandbox Code Playgroud)

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


Fre*_*ame 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;
}
Run Code Online (Sandbox Code Playgroud)

更新:我知道,你要求它,但你最好不要使用它:)

  • 我知道那是OP所要求的,但是认真......这看起来像混淆的一个奇妙的例子.假设在某些标题中定义了"TRIPLE_FOR",当我在这里看到"TRIPLE_FOR"时,我该怎么想. (5认同)
  • 是的,我想,你是对的:)我想,我会把它留在这里作为一个例子,这可以用宏完成,但添加一个注意,最好不要这样做:)我只是醒来起来,并决定用这个问题作为心灵的小热身. (2认同)

Ste*_*314 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.
}
Run Code Online (Sandbox Code Playgroud)