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 int
s or double
s:
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)
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::vector
s; 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.
have you seen that, does it help anyhow: stackoverflow.com/questions/44206965/… ?
– storaged
Sep 7 '18 at 17:45