Tengo varios vectores de int so double s:

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 };

Necesito procesar el producto cartesiano de cada vector:

for (int i : iv)
{
    for (double j : jv)
    {
        for (int k : kv)
        {
            process(i, j, k);
        }
    }
}

Me gustaría aplanar esto en una sola llamada

product(iv, jv, kv, [=](int i, double j, int k)
    {
        process(i, j, k);
    });
  • El número de vectores de entrada es variable.
  • Los tipos almacenados en los vectores de entrada son variables.

Es posible? (Estoy usando C ++ 14)

6
Steve Lorimer 7 sep. 2018 a las 20:41

4 respuestas

La mejor respuesta

Aquí hay una versión recursiva corta que solo funciona con cualquier iterable. Toma todo por const& por simplicidad:

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...);
    }   
}

Cuál podría ser:

product(process, iv, jv, kv);
6
Barry 7 sep. 2018 a las 20:40

Utiliza C ++ 14 para poder utilizar std::index_sequence / std::make_index_sequence / std::index_sequence_for ...

Propongo llamar a product() pasando primero la función y luego los vectores.

Quiero decir

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

De esta manera puede usar plantillas variadas para vectores.

Propongo también las siguientes funciones auxiliares

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...);
 }

Entonces product() simplemente se convierte

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

La idea es extraer y acumular en std::tuple los valores. Dado el std::tuple final, llame a la función que extrae los valores del std::tuple usando std::get y los índices generados con std::index_sequence_for.

Observe que no hay necesidad de que los vectores sean std::vector s; pueden ser std::queue, std::array, etc.

El siguiente es un ejemplo completo de compilación

#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);
 }

Desafortunadamente, no puede usar C ++ 17 donde puede evitar la parte std::index_sequence / std::index_sequence_for / std::get() y usar std::apply() de la siguiente manera

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...); }
3
max66 7 sep. 2018 a las 18:47

Esto es para explicar el código de Barry:

#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);
}

Esta es solo una forma elegante de llamar a process. El punto es f(c1,es...) crea una función currificada de f donde el primer parámetro está bloqueado en c1. Por lo tanto, cuando cs se vacía, todos los parámetros se curry y f() termina llamando process(1,1.0,2) Observe que este curry está disponible solo en tiempo de compilación y no en tiempo de ejecución.

0
Joseph Mariadassou 9 sep. 2018 a las 23:56

Aquí está mi solución. Probablemente no sea óptimo, pero funciona.

Una desventaja es que solo funciona con contenedores de acceso aleatorio.

Cambié la sintaxis de la llamada de product(a, b, c, lambda) a product(a, b, c)(lambda) ya que esta es más fácil de implementar.

#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
    */
}

Pruébelo en vivo

2
HolyBlackCat 7 sep. 2018 a las 18:28