У меня есть несколько векторов 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 };
Мне нужно обработать декартово произведение каждого вектора:
for (int i : iv)
{
for (double j : jv)
{
for (int k : kv)
{
process(i, j, k);
}
}
}
Я хотел бы объединить это в один звонок
product(iv, jv, kv, [=](int i, double j, int k)
{
process(i, j, k);
});
Это возможно? (Я использую C++ 14)
product(std::tuple<...>, functor );
должно быть возможно
Как насчет ericniebler.github.io/range-v3/…
Вот мое решение. Это, наверное, не оптимально, но работает.
Одним из недостатков является то, что он работает только с контейнерами с произвольным доступом.
Я изменил синтаксис вызова с 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 sizes[]{lists.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
*/
}
Вы используете C++ 14, поэтому вы можете использовать std::index_sequence
/ std::make_index_sequence
/ std::index_sequence_for
...
Предлагаю вызвать product()
, передав сначала функцию, а затем векторы.
я имею в виду
product([=](int i, double j, int k) { process(i, j, k); }, iv, jv, kv);
Таким образом, вы можете использовать вариативные шаблоны для векторов.
Предлагаю также следующие вспомогательные функции
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...);
}
так что product()
просто станет
template <typename F, typename ... Ts>
void product (F f, Ts ... ts)
{ productH(f, std::index_sequence_for<Ts...>{}, std::make_tuple(), ts...); }
Идея состоит в том, чтобы извлечь и накапливать в std::tuple
значения. Учитывая окончательный std::tuple
, вызовите функцию, извлекающую значения из std::tuple
, используя std::get
и индексы, созданные с помощью std::index_sequence_for
.
Обратите внимание, что вектора не обязательно должны быть std::vector
; это может быть std::queue
, std::array
и т. д.
Ниже приведен полный пример компиляции.
#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);
}
К сожалению, вы не можете использовать C++ 17, где вы можете избежать части 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...); }
Проголосовали за использование итераторов (через ранжирование для) вместо индексов, как это сделал я.
Это круто! Спасибо!
Вот короткая рекурсивная версия, которая работает с любыми итерациями. Для простоты от 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...);
}
}
Что было бы:
product(process, iv, jv, kv);
возможно, вы могли бы объяснить код для тех из нас, кто застрял на C++ 11 ...
@Walter Какая часть вам нужна? Если вы попытаетесь устно объяснить кому-нибудь, как составлять декартово произведение, вы, вероятно, в конечном итоге получите именно такой алгоритм. Вы можете сделать это и в C++ 11, просто нужно написать признак типа, чтобы перейти от типа контейнера к константной ссылке.
Это для объяснения кода Барри:
#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);
}
Это просто причудливый способ вызова process.
. Дело в том, что f(c1,es...)
создает каррированную функцию f
, где первый параметр привязан к c1
. Таким образом, когда cs
становится пустым, все параметры каррированы, и f()
вызывает process(1,1.0,2)
. Обратите внимание, что это каррирование доступно только во время компиляции, а не во время выполнения.
Вы это видели, это хоть как-то помогает: stackoverflow.com/questions/44206965/…?