Call lambda with the cartesian product of values in multiple input vectors

Call lambda with the cartesian product of values in multiple input vectors



I have several vectors of either ints or doubles:


int


double


std::vector<int> iv = 1, 2, 3, 4 ;
std::vector<double> jv = .5, 1., 1.5, 2. ;
std::vector<int> kv = 5, 4, 3, 2 ;



I need to process the cartesian product of each vector:


for (int i : iv)

for (double j : jv)

for (int k : kv)

process(i, j, k);





I would like to flatten this into a single call


product(iv, jv, kv, [=](int i, double j, int k)

process(i, j, k);
);



Is this possible? (I'm using C++14)






have you seen that, does it help anyhow: stackoverflow.com/questions/44206965/… ?

– storaged
Sep 7 '18 at 17:45






product(std::tuple<...>, functor ); should be possible

– Slava
Sep 7 '18 at 17:45



product(std::tuple<...>, functor );






How about ericniebler.github.io/range-v3/…

– Slava
Sep 7 '18 at 17:49




4 Answers
4



Here's a short recursive version that just works with any iterables. It takes everything by const& for simplicity:


const&


template <typename F>
void product(F f)
f();


template <typename F, typename C1, typename... Cs>
void product(F f, C1 const& c1, Cs const&... cs)
for (auto const& e1 : c1)
product([&](auto const&... es)
f(e1, es...);
, cs...);




Which would be:


product(process, iv, jv, kv);






This is a work of beauty! Thank you!

– Steve Lorimer
Sep 7 '18 at 21:04






perhaps you could explain the code for those of us stuck with C++11 ...

– Walter
Sep 7 '18 at 23:46






@Walter Which part do you need explanation for? If you try to verbally explain to somebody how to do cartesian product, this is probably the algorithm you'll end up with. You could do this in C++11 too, just need to write a type trait to go from container type to const reference.

– Barry
Sep 8 '18 at 2:27



You use C++14 so you can use std::index_sequence/std::make_index_sequence/std::index_sequence_for...


std::index_sequence


std::make_index_sequence


std::index_sequence_for



I propose to call product() passing first the function and next the vectors.


product()



I mean


product([=](int i, double j, int k) process(i, j, k); , iv, jv, kv);



This way you can use variadic templates for vectors.



I propose also the following helper functions


template <typename F, std::size_t ... Is, typename Tp>
void productH (F f, std::index_sequence<Is...> const &, Tp const & tp)
f(std::get<Is>(tp)...);

template <typename F, typename Is, typename Tp, typename T0, typename ... Ts>
void productH (F f, Is const & is, Tp const & tp, T0 const & t0, Ts ... ts)

for ( auto const & val : t0 )
productH(f, is, std::tuple_cat(tp, std::tie(val)), ts...);



so product() simply become


product()


template <typename F, typename ... Ts>
void product (F f, Ts ... ts)
productH(f, std::index_sequence_for<Ts...>, std::make_tuple(), ts...);



The idea is extract and accumulate in a std::tuple the values. Given the final std::tuple, call the function extracting the values from the std::tuple using std::get and the indexes generated with std::index_sequence_for.


std::tuple


std::tuple


std::tuple


std::get


std::index_sequence_for



Observe that there is no need that vector are std::vectors; the can be std::queue, std::array, etc.


std::vector


std::queue


std::array



The following is a full compiling example


#include <array>
#include <tuple>
#include <deque>
#include <vector>
#include <utility>
#include <iostream>

template <typename F, std::size_t ... Is, typename Tp>
void productH (F f, std::index_sequence<Is...> const &, Tp const & tp)
f(std::get<Is>(tp)...);

template <typename F, typename Is, typename Tp, typename T0, typename ... Ts>
void productH (F f, Is const & is, Tp const & tp, T0 const & t0, Ts ... ts)

for ( auto const & val : t0 )
productH(f, is, std::tuple_cat(tp, std::tie(val)), ts...);


template <typename F, typename ... Ts>
void product (F f, Ts ... ts)
productH(f, std::index_sequence_for<Ts...>, std::make_tuple(), ts...);

void process (int i1, double d1, int i2)
std::cout << '[' << i1 << ',' << d1 << ',' << i2 << ']' << std::endl;

int main ()

std::vector<int> iv = 1, 2, 3, 4 ;
std::array<double, 4u> jv = .5, 1., 1.5, 2. ;
std::deque<int> kv = 5, 4, 3, 2 ;

product([=](int i, double j, int k) process(i, j, k); , iv, jv, kv);



Unfortunately you can't use C++17 where you can avoid the std::index_sequence/std::index_sequence_for/std::get() part and use std::apply() as follows


std::index_sequence


std::index_sequence_for


std::get()


std::apply()


template <typename F, typename Tp>
void productH (F f, Tp const & tp)
std::apply(f, tp);

template <typename F, typename Tp, typename T0, typename ... Ts>
void productH (F f, Tp const & tp, T0 const & t0, Ts ... ts)

for ( auto const & val : t0 )
productH(f, std::tuple_cat(tp, std::tie(val)), ts...);


template <typename F, typename ... Ts>
void product (F f, Ts ... ts)
productH(f, std::make_tuple(), ts...);






Upvoted for using iterators (via ranged for) instead of indices as I did.

– HolyBlackCat
Sep 7 '18 at 19:00






This is awesome! Thank you!

– Steve Lorimer
Sep 7 '18 at 19:01



Here's my solution. It's probably not optimal, but it works.



One downside is that it works only with random-access containers.



I changed the call syntax from product(a, b, c, lambda) to product(a, b, c)(lambda) since this one is easier to implement.


product(a, b, c, lambda)


product(a, b, c)(lambda)


#include <cstddef>
#include <iostream>
#include <vector>
#include <utility>

template <typename ...P, std::size_t ...I>
auto product_impl(std::index_sequence<I...>, const P &...lists)

return [&lists...](auto &&func)

std::size_t sizeslists.size()...;
std::size_t indices[sizeof...(P)];
std::size_t i = 0;

while (i != sizeof...(P))

func(lists[indices[I]]...);

for (i = 0; i < sizeof...(P); i++)

indices[i]++;
if (indices[i] == sizes[i])
indices[i] = 0;
else
break;


;


template <typename ...P>
auto product(const P &...lists)

return product_impl(std::make_index_sequence<sizeof...(P)>, lists...);


int main()

std::vector<int> a = 1,2,3;
std::vector<float> b = 0.1, 0.2;
std::vector<int> c = 10, 20;

product(a, b, c)((int x, float y, int z)

std::cout << x << " " << y << " " << z << 'n';
);

/* Output:
1 0.1 10
2 0.1 10
3 0.1 10
1 0.2 10
2 0.2 10
3 0.2 10
1 0.1 20
2 0.1 20
3 0.1 20
1 0.2 20
2 0.2 20
3 0.2 20
*/



Try it live



This is by way of explaining Barry's code:


#include <iostream>
template <typename F>
void product(F f)
f();


template <typename F, typename C1, typename... Cs>
void product(F f, C1 const& c1, Cs const&... cs)
product([&] ( auto const&... es)
f(c1,es...);
,
cs...);


void process(int i, double j, int k)

std::cout << i << " " << j << " " << k << std::endl;


int main()

product(process, 1, 1.0, 2);



This is just a fancy way of calling process. The point is f(c1,es...) creates a curried function of f where the first parameter is locked in to c1. Thus when cs becomes empty, all the parameters are curried and f() lands up calling process(1,1.0,2) Notice that this currying is available only at compile time not run time.


process.


f(c1,es...)


f


c1


cs


f()


process(1,1.0,2)



Thanks for contributing an answer to Stack Overflow!



But avoid



To learn more, see our tips on writing great answers.



Required, but never shown



Required, but never shown




By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Popular posts from this blog

𛂒𛀶,𛀽𛀑𛂀𛃧𛂓𛀙𛃆𛃑𛃷𛂟𛁡𛀢𛀟𛁤𛂽𛁕𛁪𛂟𛂯,𛁞𛂧𛀴𛁄𛁠𛁼𛂿𛀤 𛂘,𛁺𛂾𛃭𛃭𛃵𛀺,𛂣𛃍𛂖𛃶 𛀸𛃀𛂖𛁶𛁏𛁚 𛂢𛂞 𛁰𛂆𛀔,𛁸𛀽𛁓𛃋𛂇𛃧𛀧𛃣𛂐𛃇,𛂂𛃻𛃲𛁬𛃞𛀧𛃃𛀅 𛂭𛁠𛁡𛃇𛀷𛃓𛁥,𛁙𛁘𛁞𛃸𛁸𛃣𛁜,𛂛,𛃿,𛁯𛂘𛂌𛃛𛁱𛃌𛂈𛂇 𛁊𛃲,𛀕𛃴𛀜 𛀶𛂆𛀶𛃟𛂉𛀣,𛂐𛁞𛁾 𛁷𛂑𛁳𛂯𛀬𛃅,𛃶𛁼

Edmonton

Crossroads (UK TV series)