C++ 编译时多个集合的笛卡尔积

C++ 编译时多个集合的笛卡尔积,c++,templates,c++17,cartesian-product,C++,Templates,C++17,Cartesian Product,我正在为一个笛卡尔积的实现而挣扎 具有给定范围的多个索引0,…,n-1 基本理念是要有一个功能: template <template <typename...> class L, typename... Ts, typename F> constexpr auto unpack(L<Ts...>, F f) { return f(Ts{}...); } template <size_t R, size_t N> constexp

我正在为一个笛卡尔积的实现而挣扎 具有给定范围的多个索引
0,…,n-1

基本理念是要有一个功能:

template <template <typename...> class L,
    typename... Ts, typename F>
constexpr auto unpack(L<Ts...>, F f) {
    return f(Ts{}...);
}

template <size_t R, size_t N>
constexpr auto cartesian_product()
{
    using P = mp_apply_q<
        mp_bind_front<mp_product_q, mp_quote<mp_list>>,
        mp_repeat_c<mp_list<mp_iota_c<R>>, N>>;

    return unpack(P{},
        [](auto... lists){
            return std::array{
                unpack(lists, [](auto... values){
                    return std::make_tuple(int(values)...);
                })...
            };
        });
}
cartesian_乘积()

使用包含保存不同产品的元组的输出数组

[(0,..,0), (0,...,1), (0,...,n-1),...., (n-1, ..., n-1)]
下面是一个简单的例子:

auto result = cartesian_product<3, 2>();
我的主要问题是,我的笛卡尔积版本很慢,如果您选择有5个以上的集合,就会产生堆栈溢出。我认为我的代码有太多的递归和临时变量

我的实现(C++17)可以在这里找到:

#包括
#包括
#包括
模板
constexpr自动展平\u元组\u i(T元组,std::index\u序列){
返回std::tuple_cat(std::get(tuple)…);
}
模板
constexpr自动展平元组(T元组){
返回flatten_tuple_i(tuple,std::make_index_sequence{});
}
模板
constexpr自动递归\u展平\u元组(T元组){
如果constexpr(深度0),“最低输入必须是笛卡尔的”);
静态断言((集合>0),“最低输入必须是笛卡尔的”);
返回ct_i(std::make_index_sequence{});
}
int main()
{
constexpr auto crt=cartesian_乘积();
用于(自动和电气:crt){

std::cout您可以轻松完成此操作,而无需递归。请注意,每个元组都是从
0
range**set
的数字,在base
range
中,因此您可以递增一个计数器(或应用于
std::index\u序列
)并逐个计算每个值

下面是一个实现(它返回
std::array
s的
std::array
,其工作原理与
std::tuple
s基本相同,因为您可以
get
tuple\u size
std::array
上的
tuple\u元素
,但如果确实需要,可以将它们转换为
std::tuple
s):

#包括
#包括


请注意,
(empty_set)^0
在这里被定义为包含一个空集的集合,但这可以通过使
ipow(0,0)==0
而不是
1
来改变,因为我也在研究一个解决方案,我认为我也发布了它(尽管与Artyer的答案非常相似).同样的前提,我们用数组替换元组,只需迭代元素,逐个递增

请注意,
power
功能已断开,因此如果需要功率值0;) { --idx; 如果(++计数器[idx]>=最大值) { setAll(counter.begin()+idx,counter.end(),0);//因为std::fill还不是constexpr } 其他的 { 打破 } } } //std::pow不是constexpr constexpr自动电源=[](自动基础,自动电源) { 自动结果=基准; 而(--功率) 结果*=基数; 返回结果; }; 模板 constexpr自动笛卡尔乘积() { std::数组产品{}; std::数组currentSet{}; 用于(汽车和产品:产品) { 产品=电流集; 增量(当前设置、静态_转换(范围)); } 退货产品; } int main() { constexpr auto crt=cartesian_乘积(); 用于(自动和电气:crt) { 用于(自动val:ele)
std::cout我只是为了好玩而尝试,结果我的想法和@Timo差不多,只是格式/风格不同

#include <iostream>
#include <array>
using namespace std;


template<size_t range, size_t sets>
constexpr auto cartesian_product() {
    // how many elements = range^sets
    constexpr auto size = []() {
        size_t x = range;
        size_t n = sets;
        while(--n != 0) x *= range;
        return x;
    }();

    auto products = array<array<size_t, sets>, size>();
    auto counter = array<size_t, sets>{}; // array of zeroes

    for (auto &product : products) {
        product = counter;

        // counter increment and wrapping/carry over
        counter.back()++;
        for (size_t i = counter.size()-1; i != 0; i--) {
            if (counter[i] == range) {
                counter[i] = 0;
                counter[i-1]++;
            }
            else break;
        }
    }
    return products;
}


int main() {
    auto prods = cartesian_product<3, 6>();
}
#包括
#包括,这是……好吧,它不是一行,但仍然不是那么糟糕:

template <typename... Lists>
using list_product = mp_product<mp_list, Lists...>;

template <typename... Ts>
constexpr auto list_to_tuple(mp_list<Ts...>) {
    return std::make_tuple(int(Ts::value)...);
}

template <typename... Ls>
constexpr auto list_to_array(mp_list<Ls...>) {
    return std::array{list_to_tuple(Ls{})...};
}

template <size_t R, size_t N>
constexpr auto cartesian_product()
{
    using L = mp_repeat_c<mp_list<mp_iota_c<R>>, N>; 
    return list_to_array(mp_apply<list_product, L>{});
}

虽然这种方法很难理解,但它是相同的算法。

如果你想在编译时使用它,你应该只在编译时数据结构上使用编译时求值。正如上面@Barry所指出的,使用Boost.Mp11可以极大地方便它。当然,你可以在你的计算机上用普通的C++17重新实现相关的基本函数拥有:

#包括
模板结构框{
使用类型=T;
};
模板结构列表{};
模板结构;
使用ConsT=typename Cons::type的模板;
模板结构Cons:Box{};
使用Nil=列表;
模板结构n;
使用NthT=typename Nth::type的模板;
模板结构n:std::tuple_元素{};
模板结构头;
使用HeadT=typename Head::type的模板;
模板结构头:框{};
模板结构尾部;
使用TailT=typename Tail::type的模板;
模板结构尾部:框{};
模板结构Concat;
使用ConcatT=typename Concat::type的模板;
模板结构Concat:Cons{};
模板结构Concat:Cons{};
模板结构Concat:Concat{};
模板结构Concat:Box{};
模板结构前置;
使用PrependT=typename Prepend::type的模板;
模板结构前置:框{};
模板结构前置:框{};
模板结构产品;
使用ProductT=typename Product::type的模板;
模板结构产品:Concat{};
模板结构产品:框{};
使用IntValue=std::integral_常量的模板;
模板结构整数序列;
使用IntegerSequenceT=typename IntegerSequence::type的模板;
模板结构整型序列:框{};
模板使用范围=整数序列;
模板结构笛卡尔立方;
使用CartesianCube=typename CartesianCube::type的模板;
模板结构CartesianCube:Product{};
模板结构CartesianCube:Box{};

模板std::ostream&operatorI我只是好奇:为什么要在编译时得到结果?@BlueTune笛卡尔乘积可以用来生成矩阵中的位置。例如a the cartesian()product生成了行和列大小为n-1的2D矩阵中的所有位置。我正在编写一个张量乘法库,我的算法就是基于此。@BlueTune,由于这些位置总是相同的,所以您可以计算所有索引(此处:元组)在编译时,以节省运行时间。您可以使用C++20吗?如果可以,我建议使用关键字
consteval
来简化代码。感谢您的提示,但gcc10尚未发布。我选择了您的答案,因为它看起来很干净
#include <iostream>
#include <array>
using namespace std;


template<size_t range, size_t sets>
constexpr auto cartesian_product() {
    // how many elements = range^sets
    constexpr auto size = []() {
        size_t x = range;
        size_t n = sets;
        while(--n != 0) x *= range;
        return x;
    }();

    auto products = array<array<size_t, sets>, size>();
    auto counter = array<size_t, sets>{}; // array of zeroes

    for (auto &product : products) {
        product = counter;

        // counter increment and wrapping/carry over
        counter.back()++;
        for (size_t i = counter.size()-1; i != 0; i--) {
            if (counter[i] == range) {
                counter[i] = 0;
                counter[i-1]++;
            }
            else break;
        }
    }
    return products;
}


int main() {
    auto prods = cartesian_product<3, 6>();
}
// given cartesian_product<3, 4>
[0, 0, 0, 0]
[0, 0, 0, 1]
[0, 0, 0, 2]
[0, 0, 1, 0] // carry over
...
...
[2, 2, 2, 2]
template <typename... Lists>
using list_product = mp_product<mp_list, Lists...>;

template <typename... Ts>
constexpr auto list_to_tuple(mp_list<Ts...>) {
    return std::make_tuple(int(Ts::value)...);
}

template <typename... Ls>
constexpr auto list_to_array(mp_list<Ls...>) {
    return std::array{list_to_tuple(Ls{})...};
}

template <size_t R, size_t N>
constexpr auto cartesian_product()
{
    using L = mp_repeat_c<mp_list<mp_iota_c<R>>, N>; 
    return list_to_array(mp_apply<list_product, L>{});
}
template <template <typename...> class L,
    typename... Ts, typename F>
constexpr auto unpack(L<Ts...>, F f) {
    return f(Ts{}...);
}

template <size_t R, size_t N>
constexpr auto cartesian_product()
{
    using P = mp_apply_q<
        mp_bind_front<mp_product_q, mp_quote<mp_list>>,
        mp_repeat_c<mp_list<mp_iota_c<R>>, N>>;

    return unpack(P{},
        [](auto... lists){
            return std::array{
                unpack(lists, [](auto... values){
                    return std::make_tuple(int(values)...);
                })...
            };
        });
}