清洁的方法来写多个“for”循环
对于一个有多个维度的数组,我们通常需要为每个维度写一个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
循环。 如何使用macros来定义for-for-for
循环,以便我不需要每次都重写这种types的代码? 有一个更好的方法吗?
首先是你不使用这样的数据结构。 如果你需要一个三维matrix,你可以定义一个:
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)
如果在迭代过程中需要三个索引,可以创build一个迭代器来公开它们:
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); } // ... }; };
使用macros来隐藏for
循环可能会让人困惑,只是为了节省很less的字符。 我会使用范围for循环代替:
for (auto& k : A) for (auto& i : k) for (auto& j : i) do_something_on_A(j);
当然,你可以使用const auto&
来代替auto&
,事实上,如果你不修改数据。
像这样的东西可以帮助:
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,我们需要一些模板魔法。 首先我们应该创buildSFINAE结构来区分这个值还是容器。 值的默认实现,以及数组和每种容器types的特化。 @Zeta如何指出,我们可以通过嵌套iterator
types来确定标准容器(理想情况下,我们应该检查该types是否可以与range-base一起使用)。
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, }
这也不适用于指针(在堆中分配的数组)。
大部分的答案只是演示如何将C ++转换成难以理解的语法扩展,恕我直言。
通过定义任何模板或macros,你只要强迫其他程序员理解被混淆的代码的位,以隐藏其他位的混淆代码。
你会强迫每个读你代码的人都拥有模板专业知识,只是为了避免你用清晰的语义来定义对象。
如果你决定使用像3维数组这样的原始数据,就直接使用它,否则定义一个给你的数据一些可理解的含义的类。
for (auto& k : A) for (auto& i : k) for (auto& current_A : i) do_something_on_A(current_A);
只是与没有明确语义的int向量向量的隐含定义一致。
#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; }
更新:我知道,你要求,但你最好不要使用:)
一个想法是编写一个可迭代的伪容器类,它包含您要索引的所有多索引元组的集合。 在这里没有实现,因为它会花费太长的时间,但想法是,你应该能够写…
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. }
我用下面的语句告诉这个答案: 这只会工作,如果你在一个实际的数组上运行 – 它不会为你的例子使用std::vector
。
如果对multidimensional array的每个元素执行相同的操作,而不关心每个项目的位置,那么可以利用数组放在连续的内存位置,并将整个事物视为一个大一维数组。 例如,如果我们想在第二个例子中将每个元素乘以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);
我通常不会build议这种方法 (比较喜欢Jefffrey的答案),因为它依赖于为您的数组定义大小,但在某些情况下,它可能是有用的。
我在这里看到很多答案recursion的工作,检测input是否是一个容器。 相反,为什么不检测当前图层是否与函数所用的types相同? 它更简单,并允许更强大的function:
//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来检测当前input是否适合函数
//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技巧来使它更喜欢第一个函数,也就是传递一个零,并且让我们更喜欢accept和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 ); };
这里和这里编译和执行的certificate
如果你想在C ++ 11中使用更方便的语法,你可以添加一个macros。 (以下未经testing)
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 << ", "; } }
我有些震惊,没有人提出一些基于算术魔术的循环来完成这项工作。 由于C.王正在寻找一个没有嵌套循环的解决scheme ,我会提出一个:
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; } }
这个模板函数也可以用嵌套循环的forms来表示:
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); } } } }
并且可以使用提供任意大小的三维数组加上函数名称,让参数推演做好每个维数大小的计算:
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我们可以为任意维的数组完成工作,首先我们需要一个模板函数迭代秩为 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); } }
另一个迭代任何级别的数组,做recursion:
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
对于多重嵌套向量,解决scheme重新组合任意大小任意大小的数组之一,但没有SFINAE:首先,我们需要一个迭代std::vector
s并调用所需函数的模板函数:
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); } }
另一个模板函数迭代任何vectorvector并调用自己:
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
都会调用vectorvector版本,除非vector值版本是更好的匹配,从而结束recursion。
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; }
我认为函数体是非常简单和直接的…我不知道编译器是否可以展开这个循环(我几乎可以肯定,大多数编译器可以展开第一个例子)。
在这里看到现场演示 。
希望能帮助到你。
沿着这些线使用一些东西(它的伪代码,但是这个想法保持不变)。 您提取模式以循环一次,并且每次应用不同的函数。
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)
坚持嵌套for循环!
这里提出的所有方法在可读性或灵活性方面都有缺点。
如果您需要使用内部循环的结果来处理外部循环,会发生什么情况? 如果在内部循环中需要来自外部循环的值,会发生什么情况? 大多数“封装”方法在这里失败。
相信我我已经看到了几个尝试“清理”嵌套for循环,最后事实certificate,嵌套循环实际上是最干净和最灵活的解决scheme。
我使用的一种技术是模板。 例如:
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)
。 模板函数为每个维创build一次,第一次使用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
types的任何东西都是更好的匹配。
您可以像这样在一个循环中生成索引(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; }
有一件事你可能想尝试,如果你只在最内层循环中有语句,并且你关心的更多是关于代码的过于冗长的本质,那就是使用不同的空白scheme。 这只有在你能够足够紧凑地声明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 ++之外,并不需要任何知识(比如macros约定),也不需要像macros那样的技巧。
如果你真的想要一个macros,你可以进一步采取这样的事情:
#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]); }
希望你可以很容易地告诉哪些陈述与哪些陈述。 另外,注意逗号,现在你不能在任何一个单独的子句中使用它们。
这是一个C ++ 11的实现,处理一切迭代。 其他解决scheme使用::iterator
typedefs或arrays来限制自己的容器:但是for_each
是关于迭代的,而不是一个容器。
我也将SFINAE分离到is_iterable
特征中的一个位置。 调度(元素和迭代之间)是通过标签调度完成的,我发现这是一个更清晰的解决scheme。
容器和应用于元素的函数都是完美转发的,允许const
和non const
访问范围和函子。
#include <utility> #include <iterator>
我正在实现的模板function。 其他一切都可以进入一个详细的名字空间:
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
所需的一些样板is_iterable
。 我在begin
和end
的详细命名空间做参数依赖查找。 这模拟了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
用于testing代码是否有效。 你做TypeSink< decltype(
code ) >
,如果code
是有效的,expression式是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{};
我只testingbegin
。 adl_end
testing也可以完成。
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>() ); }
现场示例
这是在底部的方式:随意挖掘最好的答案,这是坚实的。 我只是想要使用一些更好的技术!
首先,你不应该使用vectorvector的vector。 每个vector保证有连续的内存,但vectorvector的“全局”内存不(也可能不会)。 你应该使用标准的库types数组,而不是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]); } } }
更好的是,你可以定义一个简单的3Dmatrix类:
#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; }
你可以走得更远,使它完全正确的正确,matrix乘法(正确和元素明智),乘以向量等,你甚至可以推广到不同types(如果你主要使用双打,我会使它成为模板) 。
您还可以添加代理对象,以便您可以执行B [i]或B [i] [j]。 他们可以返回vector(在math意义上)和matrix充满双倍,可能?