For an array with multiple dimensions, we usually need to write a for
loop for each of its dimensions. For example:
vector< vector< vector
Using a macro to hide the for
loops can be a lot confusing, just to save few characters. I'd use range-for loops instead:
for (auto& k : A)
for (auto& i : k)
for (auto& j : i)
do_something_on_A(j);
Of course you can replace auto&
with const auto&
if you are, in fact, not modifying the data.
Use something along these lines (its pseudo-code, but the idea stays the same). You extract the pattern to loop once, and apply a different function each time.
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)
One technique I've used is templates. 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
}
Then you simply call do_something_on_A(A)
in your main code. The template function gets created once for each dimension, the first time with T = std::vector<std::vector<int>>
, the second time with with T = std::vector<int>
.
You could make this more generic using std::function
(or function-like objects in C++03) as a second argument if you want:
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);
}
Then call it like:
do_something_on_vec(A, std::function(do_something_on_A));
This works even though the functions have the same signature because the first function is a better match for anything with std::vector
in the type.
I was kind of shocked that no one proposed some arithmetic-magic based loop to do the work. Since C. Wang is looking for a solution with no nested loops, I'll propose one:
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;
}
Well, this approach isn't elegant and flexible, so we could pack all the process into a template function:
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;
}
}
This template function can be expressed in the form of nested loops as well:
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);
}
}
}
}
And can be used providing a 3D array of arbitrary size plus the function name, letting the parameter deduction do the hard work of counting the size of each dimension:
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;
}
But once again, it lacks of flexibility 'cause it only works for 3D arrays, but using SFINAE we can do the work for arrays of an arbitrary dimension, first we need a template function which iterates arrays of rank 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);
}
}
And another one which iterates arrays of any rank, doing the 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);
}
}
This allows us to iterate all the elements in all the dimensions of a arbitrary-dimensions arbitrary-sized array.
std::vector
For the multiple nested vector, the solution ressembles the one of arbitrary-dimensions arbitrary-sized array, but without SFINAE: First we will need a template function that iterates std::vector
s and calls the desired function:
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);
}
}
And another template function that iterates any kind of vector of vectors and calls himself:
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);
}
}
Regardless of the nesting level, iterate_all
will call the vector-of-vectors version unless the vector-of-values version is a better match thus ending the recursivity.
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;
}
I think that the function body is pretty simple and straight-forward... I wonder if the compiler could unroll this loops (I'm almost sure that most compilers could unroll the first example).
See live demo here.
Hope it helps.
Most of the answers simply demonstrate how C++ can be twisted into incomprehensible syntactic extensions, IMHO.
By defining whatever templates or macros, you just force other programmers to understand bits of obfuscated code designed to hide other bits of obfuscated code.
You will force every guy who reads your code to have template expertise, just to avoid doing your job of defining objects with clear semantics.
If you decided to use raw data like 3 dimensional arrays, just live with it, or else define a class that gives some understandable meaning to your data.
for (auto& k : A)
for (auto& i : k)
for (auto& current_A : i)
do_something_on_A(current_A);
is just consistent with the cryptic definition of a vector of vector of vector of int with no explicit semantics.
I caveat this answer with the following statement: this would only work if you were operating on an actual array - it wouldn't work for your example using std::vector
.
If you are performing the same operation on every element of a multi-dimensional array, without caring about the position of each item, then you can take advantage of the fact that arrays are placed in contiguous memory locations, and treat the whole thing as one big one-dimensional array. For example, if we wanted to multiply every element by 2.0 in your second example:
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;
}
Note that using the above approach also allows the use of some "proper" C++ techniques:
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);
I don't generally advise this approach (preferring something like Jefffrey's answer), as it relies on having defined sizes for your arrays, but in some cases it can be useful.