Cartesian Product using Iterators and Variadic Templates

≡放荡痞女 提交于 2019-12-05 07:46:14

Here's what I've come up with:

#include <iostream>
#include <tuple>
#include <vector>

template <typename T, typename B>
bool increment(const B& begins, std::pair<T,T>& r) {
  ++r.first;
  if (r.first == r.second) return true;
  return false;
}
template <typename T, typename... TT, typename B>
bool increment(const B& begins, std::pair<T,T>& r, std::pair<TT,TT>&... rr) {
  ++r.first;
  if (r.first == r.second) {
    r.first = std::get<std::tuple_size<B>::value-sizeof...(rr)-1>(begins);
    return increment(begins,rr...);
  }
  return false;
}

template <typename OutputIterator, typename... Iter>
void cartesian_product(
  OutputIterator out,
  std::pair<Iter,Iter>... ranges
) {
  const auto begins = std::make_tuple(ranges.first...);
  for (;;) {
    out = { *ranges.first... };
    if (increment(begins, ranges...)) break;
  }
}

struct foo {
  int i;
  char c;
  float f;
};

int main(int argc, char* argv[]) {

  std::vector<int> ints { 1, 2, 3 };
  std::vector<char> chars { 'a', 'b', 'c' };
  std::vector<float> floats { 1.1, 2.2, 3.3 };

  std::vector<foo> product;

  cartesian_product(
    std::back_inserter(product),
    std::make_pair(ints.begin(), ints.end()),
    std::make_pair(chars.begin(), chars.end()),
    std::make_pair(floats.begin(), floats.end())
  );

  for (const auto& x : product)
    std::cout << x.i << ' ' << x.c << ' ' << x.f << std::endl;

}

The cartesian_product function has a slightly different signature than yours, but it should be straightforward to write a wrapper.

Since the ranges you pass in may potentially have different extents, I'd suggest you pass both begin and end, as in my example.

In C++17, we get std::apply(). A possible C++14 implementation is found on that link. We can then implement fmap for a tuple as:

template <class Tuple, class F>
auto fmap(Tuple&& tuple, F f) {
    return apply([=](auto&&... args){
        return std::forward_as_tuple(f(std::forward<decltype(args)>(args))...);
    }, std::forward<Tuple>(tuple));
}

With that:

auto deref_all = fmap(iterators, [](auto it) -> decltype(auto) { return *it; });
auto incr_all = fmap(iterators, [](auto it) { return ++it; });

I recently came up with the solution that allows to invoke a callable object (e.g., lambda) for any combination of the Cartesian product of the input ranges defined by iterators. The lambda makes elements of input ranges accessible by values or by references. Exemplary usage:

std::vector<int> vector = { 1, 2, 3 };
std::set<double> set = { -1.0, -2.0 };
std::string string = "abcd";
bool array[] = { true, false };

std::cout << std::boolalpha;
cartesian_product([](const auto& v1, const auto& v2, const auto& v3, const auto& v4){
        std::cout << "(" << v1 << ", " << v2 << ", " << v3 << ", " << v4 << ")\n";
    },
    std::begin(vector), std::end(vector),
    std::begin(set), std::end(set),
    std::begin(string), std::end(string),
    std::begin(array), std::end(array)
);

I haven't found a solution with such a (natural) syntax (the style of the STL you ask for). The cartesian_product function is in my case built upon C++17 std::apply as follows:

template <typename F, typename... Ts>
void cartesian_product_helper(F&& f, std::tuple<Ts...> t) { std::apply(f, t); }

template <typename F, typename... Ts, typename Iter, typename... TailIters>
void cartesian_product_helper(
    F&& f, std::tuple<Ts...> t, Iter b, Iter e, TailIters... tail_iters)
{
  for (auto iter = b; iter != e; ++iter)
    cartesian_product_helper(
        std::forward<F>(f), std::tuple_cat(t, std::tie(*iter)), tail_iters...);
}

template <typename F, typename... Iters>
void cartesian_product(F&& f, Iters... iters) {
  cartesian_product_helper(std::forward<F>(f), std::make_tuple(), iters...);
}

It's relatively simple - it iterates over all ranges recursively and in each iteration, it appends the reference to the corresponding dereferenced iterator (i.e., range item) to the tuple. When the tuple is complete (has references to items from all levels), then the callable object is invoked and those references from the tuple are used as arguments.

Just I'm not sure whether this is the most efficient way, so any suggestions for improvement would be helpful.

Live demo is here: https://wandbox.org/permlink/lgPlpKXRkPuTtqo8

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!