5

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++?

gsamaras
  • 71,951
  • 46
  • 188
  • 305
Joonatan Samuel
  • 641
  • 4
  • 17
  • 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). – Joonatan Samuel Sep 26 '17 at 09:35
  • There is the [experimental TS for ranges](http://en.cppreference.com/w/cpp/experimental/ranges), and there is [a range library](https://github.com/ericniebler/range-v3). 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`](http://en.cppreference.com/w/cpp/utility/integer_sequence)? – Some programmer dude Sep 26 '17 at 09:40
  • What does this code even do? Can't it be replaced with a function with 4 arguments? – user7860670 Sep 26 '17 at 09:47
  • 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. – Joonatan Samuel Sep 26 '17 at 09:56

4 Answers4

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; } ); } );
}
schorsch312
  • 5,553
  • 5
  • 28
  • 57
  • 1
    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" – Caleth Sep 26 '17 at 10:59
  • I agree, but I think option 2 is still ok. Nevertheless, all automated formater would not leave it as one line. – schorsch312 Sep 26 '17 at 11:10
2

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

user7860670
  • 35,849
  • 4
  • 58
  • 84
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.

gsamaras
  • 71,951
  • 46
  • 188
  • 305
  • How would I go about implementing this library? Or do you know of a library that does this comfortably? – Joonatan Samuel Sep 26 '17 at 10:13
  • 1
    @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](https://github.com/ericniebler/range-v3), but still.. – gsamaras Sep 26 '17 at 10:17
  • 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 :) – Joonatan Samuel Sep 26 '17 at 10:23
  • @JoonatanSamuel how many times did you read 7D structures? ;) Not many I suppose. I upvoted your question though! You are welcome! – gsamaras Sep 26 '17 at 10:25
  • 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. https://gist.github.com/Jonksar/4cbb29e09344d8a18fb73ee3b0f3bbf0 – Joonatan Samuel Sep 26 '17 at 10:45
  • @JoonatanSamuel I see. I also see the answer of schorsch312, which of course if possbie, but I thought that was obvious..Anyway, thanks! – gsamaras Sep 26 '17 at 11:56
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
Shakiba Moshiri
  • 21,040
  • 2
  • 34
  • 44