One-liner for nested for loops in C++
Asked Answered
C

5

6

In Python I can do this:

>>> import itertools
>>> for i, j,  in itertools.product(range(3), repeat=2): print i, j
...
0 0
0 1
0 2
1 0
1 1
1 2
2 0
2 1
2 2

Is it possible to have an easy-to-read, non-boost version of this in C++?

Cohbath answered 26/9, 2017 at 9:34 Comment(4)
I have looked far and near for this answer however was not able to locate it. Let me know nicely if it is a duplicate (it probably is).Cohbath
There is the experimental TS for ranges, and there is a range library. But without the help of those it's not something that can be done easily as a "one-liner". Perhaps it can be done using std::integer_sequence?Rattlesnake
What does this code even do? Can't it be replaced with a function with 4 arguments?Goldthread
range(3) constructs an iterator of [0, 1, 2]. itertools.product(*args, repeat=n) takes Cartesian product of this yielding iterator to tuples of (i_1, i_2, ...i_n). These elements get assigned to variables i, j k .. etc. Which I then print out.Cohbath
G
3

Looping example (updated):

#include <array>
#include <iostream>
#include <utility>

template<int VRange, int VRepCount, int VValueRIndex = VRepCount> class
t_Looper
{
    public: template<typename TAction> static void
    process(::std::array<int, VRepCount> & values, TAction && action)
    {
        for(;;)
        {
            t_Looper<VRange, VRepCount, VValueRIndex - 1>::process(values, ::std::forward<TAction>(action));
            auto & value{values[VRepCount - VValueRIndex]};
            if((VRange - 1) != value)
            {
                ++value;
            }
            else
            {
                value = 0;
                break;
            }
        }
    }
};

template<int VRange, int VRepCount> class
t_Looper<VRange, VRepCount, 0>
{
    private: template<int... VIndexes, typename TAction> static void
    invoke(::std::integer_sequence<int, VIndexes...>, ::std::array<int, VRepCount> const & values, TAction && action)
    {
        action(values[VIndexes]...);
    }

    public: template<typename TAction> static void
    process(::std::array<int, VRepCount> & values, TAction && action)
    {
        invoke(::std::make_integer_sequence<int, VRepCount>(), values, ::std::forward<TAction>(action));
    }
};

template<int VRange, int VRepCount, typename TAction> void
multiloop(TAction && action)
{
    ::std::array<int, VRepCount> values{};
    t_Looper<VRange, VRepCount>::process(values, ::std::forward<TAction>(action));
}

int main()
{
    multiloop<3, 2>([](int i, int j){::std::cout << i << " " << j << ::std::endl;});
    multiloop<3, 4>([](int i, int j, int k, int l){::std::cout << i << " " << j << " " << k << " " << l << ::std::endl;});
    return(0);
}

Run this code online

Goldthread answered 26/9, 2017 at 11:20 Comment(3)
This is lovely.Cohbath
you can use std::array<int, N> instead of t_IntsTuple<N>Paymar
@Paymar Well, this was just a quick attempt. for(;;) loop probably can be improved as well by removing duplication somehow.Goldthread
W
3

Ranges are not avalible, but range based loops come quite close.

#include <iostream>
int main(){
    for (int i:{1,2,3}) { for (int j:{1,2,3}) {std::cout << i << " " << j <<std::endl;}};
}

or if you like to use the same range

#include <iostream>
int main(){
    const auto range {1,2,3};
    for (int i:range) {for (int j:range) {std::cout << i << " " << j <<std::endl;}};
}

and just for the fun of it with std::for_each (this one is perhaps hard to read, but it has no hand written loops)

#include <iostream>
#include <algorithm>
int main(){
    const auto range {1,2,3};
    std::for_each(range.begin(), range.end(), [range](int i) {std::for_each(range.begin(), range.end(), [i](int j) {std::cout << i << " " << j <<std::endl; } ); } );
}
Wive answered 26/9, 2017 at 9:55 Comment(2)
You can stuff it into one line with a traditional for(;;): for(const auto range {1,2,3};std::for_each(...), false;) although I doubt that counts as "easy to read"Paymar
I agree, but I think option 2 is still ok. Nevertheless, all automated formater would not leave it as one line.Wive
G
3

Looping example (updated):

#include <array>
#include <iostream>
#include <utility>

template<int VRange, int VRepCount, int VValueRIndex = VRepCount> class
t_Looper
{
    public: template<typename TAction> static void
    process(::std::array<int, VRepCount> & values, TAction && action)
    {
        for(;;)
        {
            t_Looper<VRange, VRepCount, VValueRIndex - 1>::process(values, ::std::forward<TAction>(action));
            auto & value{values[VRepCount - VValueRIndex]};
            if((VRange - 1) != value)
            {
                ++value;
            }
            else
            {
                value = 0;
                break;
            }
        }
    }
};

template<int VRange, int VRepCount> class
t_Looper<VRange, VRepCount, 0>
{
    private: template<int... VIndexes, typename TAction> static void
    invoke(::std::integer_sequence<int, VIndexes...>, ::std::array<int, VRepCount> const & values, TAction && action)
    {
        action(values[VIndexes]...);
    }

    public: template<typename TAction> static void
    process(::std::array<int, VRepCount> & values, TAction && action)
    {
        invoke(::std::make_integer_sequence<int, VRepCount>(), values, ::std::forward<TAction>(action));
    }
};

template<int VRange, int VRepCount, typename TAction> void
multiloop(TAction && action)
{
    ::std::array<int, VRepCount> values{};
    t_Looper<VRange, VRepCount>::process(values, ::std::forward<TAction>(action));
}

int main()
{
    multiloop<3, 2>([](int i, int j){::std::cout << i << " " << j << ::std::endl;});
    multiloop<3, 4>([](int i, int j, int k, int l){::std::cout << i << " " << j << " " << k << " " << l << ::std::endl;});
    return(0);
}

Run this code online

Goldthread answered 26/9, 2017 at 11:20 Comment(3)
This is lovely.Cohbath
you can use std::array<int, N> instead of t_IntsTuple<N>Paymar
@Paymar Well, this was just a quick attempt. for(;;) loop probably can be improved as well by removing duplication somehow.Goldthread
K
1

Is it possible to have an easy-to-read, non-boost version of this in C++?

No.

You cannot do that in pure C++. You would need a library or so.

There is an Extension for ranges that is experimental in C++14, but even with this, I am not sure if could make it.

Kendrakendrah answered 26/9, 2017 at 9:44 Comment(6)
How would I go about implementing this library? Or do you know of a library that does this comfortably?Cohbath
@JoonatanSamuel I mean using a library. If could write the library to do the trick, you could write the code too! ;) I am not aware of one, since I preffer a double for loop, since it's more clear and communicates to the reader that this a double loop, not a single one. You can try this range library, but still..Kendrakendrah
I do agree with clarity in most cases but if you are reading 7 dimensional data structures this gets messy very fast. Breaking out of these 7 loops produces a lot of unnecessary code. Thanks for your input :)Cohbath
@JoonatanSamuel how many times did you read 7D structures? ;) Not many I suppose. I upvoted your question though! You are welcome!Kendrakendrah
Quite often since I do work a lot with data. Just a sidenote, this way of writing makes easier to convert between different dimensional analysis. For example imagine having to write this script to produce 3D maps instead of 2D maps. Much easier to read and convert than it would be otherwise. gist.github.com/Jonksar/4cbb29e09344d8a18fb73ee3b0f3bbf0Cohbath
@JoonatanSamuel I see. I also see the answer of schorsch312, which of course if possbie, but I thought that was obvious..Anyway, thanks!Kendrakendrah
M
1

if you do not mind creating your own .. dot-dot operator with the help of these two template functions:

template< int First, int Last , int Step = 1>
int ( &dotdot() )[ ( Step + Last - First ) /  Step ]
{
    static int result[ ( Step + Last - First ) /  Step ];

    for( int index = First; index <= Last; index += Step ){
        result[ ( index - First ) / Step ] = index;
    }

    return result;
}

template< int Last, int First, int Step = 1 >
int ( &dotdot() )[ ( Step + Last - First ) / Step ]
{
    static int result[ ( Step + Last - First ) / Step ];

    for( int index = Last; index >= First; index -= Step ){
        result[ ( Last - index ) / Step ] = index;
    }

    return result;
}

then you can:

for( int i : dotdot<0,2>() ) for( int j : dotdot<0,2>() ) std::cout << i << ' ' << j << '\n';

and the output:

0 0
0 1
0 2
1 0
1 1
1 2
2 0
2 1
2 2

usage:

  • dotdot<'a','z'>() returns a to z
  • dotdot<'z','a',2>() returns z to a and step is 2
  • dotdot<-10,0>() returns -10 to 0
  • dotdot<-10,10,3>() returns -10 to 10 and step is 3
Marsipobranch answered 26/9, 2017 at 11:51 Comment(0)
M
1

Since C++23 you can use the cartesian_product standard library function. Combined with the C++20 range factory, you can easily write an equivalent of your python code like this:

#include <iostream>
#include <ranges>

using std::ranges::views::iota, std::views::cartesian_product;

int main() {
    auto range = iota(0, 3);
    for (auto const& [i, j] : cartesian_product(range, range))
        std::cout << i << ' ' << j << std::endl;
}
Musset answered 4/10 at 16:52 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.