使用迭代器和Variadic模板的笛卡尔积

时间:2017-05-26 17:16:00

标签: c++ c++11 c++14

我正在尝试使用STL的样式创建一个函数来生成可变数量输入范围的笛卡尔积。我的基本格式是函数接受固定范围和输出范围的开始,然后是可变数量的双向输入迭代器。

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

我对args的想法是我从中tuple,然后我遍历tuple来提取元素。这需要我遵循几个基本步骤:

  1. tuple
  2. 创建args
  3. 取消引用新创建的tuple
  4. 中的每个迭代器
  5. 按顺序递增tuple中的每个迭代器,以便我们获得范围中值的所有可能组合。
  6. 详细说明第3步:如果我们有两组A = {0,1}且B = {2,3},则笛卡尔乘积A x B = {(0,2),(0,3), (1,2),(1,3)}。

    我可以像第一步那样做:

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

    第二步,我不太确定。我想我会以某种方式push_back元素到一个临时元组,然后将*result设置为等于那个临时元组。我对ostream完成此任务的方式有点启发,所以我认为这可以派上用场:

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

    第三步可能非常简单。我可以结合这样的事情:

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

    此处有for_each的{​​{1}}的3,000个实施中的一个。

    可能的是,我没有正确地利用C ++ 14。到目前为止,我的教育完全依赖于C ++ 11中较不困难的部分。

    如果你想建议我使用tuple,谢谢,但我宁愿不使用它。

3 个答案:

答案 0 :(得分:4)

在C ++ 17中,我们得到std::apply()。在该链接上可以找到可能的C ++ 14实现。然后,我们可以为fmap实施tuple

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

有了这个:

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

答案 1 :(得分:3)

以下是我提出的建议:

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

}

cartesian_product函数与您的签名略有不同,但编写包装器应该很简单。

由于您传入的范围可能具有不同的范围,因此我建议您同时传递beginend,如我的示例所示。

答案 2 :(得分:1)

我最近想出了一种解决方案,它允许为迭代器定义的输入范围的笛卡尔积的任何组合调用可调用对象(例如lambda)。 lambda使输入范围的元素可以通过值或引用进行访问。示范用法:

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

我还没有找到一种具有(自然)语法(您要求的STL样式)的解决方案。在我的情况下,cartesian_product函数是基于C ++ 17 std::apply构建的,如下所示:

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

这是相对简单的-递归地遍历所有范围,并且在每次迭代中,都将引用附加到对应的已取消引用的迭代器(即范围项)中。当元组完成(具有对所有级别的项目的引用)时,将调用可调用对象,并将来自元组的那些引用用作参数。

我不确定这是否是最有效的方法,因此任何改进建议都将有所帮助。

实时演示在这里:https://wandbox.org/permlink/lgPlpKXRkPuTtqo8