Cartesian Product using Iterators and Variadic Templates

馋奶兔 提交于 2019-12-07 03:47:50

问题


I'm trying to create a function to generate the Cartesian product of a variable number of input ranges, using the style of the STL. My basic format is that the function accepts a fixed range and the start of an output range, then a variadic number of bidirectional input iterators.

template <
    typename BidirectionalIterator,
    typename OutputIterator,
    typename... Args
>
void cartesian_product(
    BidirectionalIterator first,
    BidirectionalIterator last,
    OutputIterator result,
    Args&&... args
);

My idea for the args is that I make a tuple out of it, then I iterate through that tuple to extract the elements. This would require me to follow a few basic steps:

  1. Make a tuple from args
  2. Dereference each iterator in the newly created tuple
  3. Increment each iterator in the tuple in sequence, so that we get all possible combinations of the values in the ranges.

To elaborate on step 3: if we had two sets A = {0, 1} and B = {2, 3}, the Cartesian product A x B = {(0, 2), (0, 3), (1, 2), (1, 3)}.

I can do the first step like:

auto arg_tuple = std::make_tuple(std::forward<Args>(args)...);

The second step, I'm not too sure about. I think I will have somehow push_back elements to a temporary tuple, then set *result equal to that temporary tuple. I was a little inspired by the way that ostream accomplishes this, so I think this could come in handy:

template <typename Tuple, typename T>
auto operator<<(const Tuple &lhs, const T &rhs)
    -> decltype(std::tuple_cat(lhs, std::make_tuple(rhs)))
{
    return std::tuple_cat(lhs, std::make_tuple(rhs));
}

The third step is probably pretty trivial. I could combine something like this:

template <typename T>
auto pre_increment(T &x) -> decltype(++x) {
    return ++x;
}

with one of the 3,000 implementations of for_each for a tuple that are on here.

Odds are that I'm not correctly leveraging C++14 for this. My education has been entirely on the less-difficult parts of C++11 so far.

If you're tempted to recommend I use boost::fusion for this, thanks, but I would prefer to not use it.


回答1:


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.




回答2:


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



回答3:


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



来源:https://stackoverflow.com/questions/44206965/cartesian-product-using-iterators-and-variadic-templates

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