从模板包生成大小为N的所有子包

时间:2015-02-13 16:27:43

标签: c++ templates c++11 variadic-templates combinatorics

NSubsets<N, Pack<Types...>>::type是包含所有大小为N的Types...子集的包。 例如,

NSubsets<2, Pack<int, char, double>>::type

Pack<Pack<int, char>, Pack<int, double>, Pack<char, double>>

一种方法是简单地从Obtaining all subpacks from a pack获取PowerSet解决方案的输出, 然后删除每个不是N大小的包。但这对于大N来说效率太低(无论如何都很糟糕)。这是我的想法(灵感来自PowerSet的优雅解决方案):     假设我们有Pack<A,B,C,D>,N = 2.从Pack<>开始,我们遍历Pack<A,B,C,D>中的类型并附加每个类型,如下所示: 在附加任何内容之前,我们有:

Pack<>

将A添加到上一个(并保留前一个),我们得到:

Pack<>, Pack<A>

将B附加到上一个(并保留前一个),我们得到:

Pack<>, Pack<A>, Pack<B>, Pack<A,B>,

但是Pack<A,B>的大小为2,因此将其隐藏起来并从此列表中删除,留下我们:

Pack<>, Pack<A>, Pack<B>

将C附加到上一个(并保留前一个),我们得到:

Pack<>, Pack<A>, Pack<B>, Pack<C>, Pack<A,C>, Pack<B,C>.

如上所述,藏匿Pack<A,C>, Pack<B,C>

Pack<>, Pack<A>, Pack<B>, Pack<C>

将D附加到上一个(并保留前一个),我们得到:

Pack<D>, Pack<A,D>, Pack<B,D>, Pack<C,D>.

再次选择2号,我们终于有了

Pack<Pack<A,B>, Pack<A,C>, Pack<B,C>, Pack<A,D>, Pack<B,D>, Pack<C,D>>

作为我们期望的输出。

请注意,此算法中的一个缺陷是无条件地将Pack<>保留在倒数第二步中。如果N大于2,这个无关的部分可能真的浪费时间。     以下是我使用上述方法的代码,但输出结果为false,我无法追踪原因(尚未)。但即便如此 如果它确实正常工作,我仍然不太喜欢它,主要是因为我刚才提到的缺陷,我不知道如何 除去那个瑕疵。

#include <iostream>
#include <type_traits>

template <int, typename> struct IsSize;

template <int N, template <typename...> class P, typename... Types>
struct IsSize<N, P<Types...>> : std::integral_constant<bool, sizeof...(Types) == N> {};

template <int, typename, typename, typename> struct PartitionPacksBySizeHelper;

template <int N, template <typename...> class P, typename... KeptPacks, typename... SizeNPacks>
struct PartitionPacksBySizeHelper<N, P<>, P<KeptPacks...>, P<SizeNPacks...>> {
    using not_sizeN_types = P<KeptPacks...>;
    using sizeN_types = P<SizeNPacks...>;
};

template <int N, template <typename...> class P, typename First, typename... Rest, typename... KeptPacks, typename... SizeNPacks>
struct PartitionPacksBySizeHelper<N, P<First, Rest...>, P<KeptPacks...>, P<SizeNPacks...>> : std::conditional<IsSize<N, First>::value,
        PartitionPacksBySizeHelper<N, P<Rest...>, P<KeptPacks...>, P<SizeNPacks..., First>>,
        PartitionPacksBySizeHelper<N, P<Rest...>, P<KeptPacks..., First>, P<SizeNPacks...>>
    >::type {};

template <int, typename> struct PartitionPacksBySize;

template <int N, template <typename...> class P, typename... Packs>
struct PartitionPacksBySize<N, P<Packs...>> : PartitionPacksBySizeHelper<N, P<Packs...>, P<>, P<>> {};

template <typename, typename> struct Append;

template <typename T, template <typename...> class P, typename...Types>
struct Append<T, P<Types...>> {
    using type = P<Types..., T>;
};

template <int, typename, typename, typename> struct NSubsetsHelper;

template <int N, template <typename...> class P, typename... CurrentPacks, typename... AccumulatedPacks>
struct NSubsetsHelper<N, P<>, P<CurrentPacks...>, P<AccumulatedPacks...>> {
    using type = P<AccumulatedPacks...>;
};

template <int N, template <typename...> class P, typename First, typename... Rest, typename... KeptPacks, typename... SizeNPacks>
struct NSubsetsHelper<N, P<First, Rest...>, P<KeptPacks...>, P<SizeNPacks...>>
    : NSubsetsHelper<N, P<Rest...>,
        typename PartitionPacksBySize<N, P<KeptPacks..., typename Append<First, KeptPacks>::type...>>::not_sizeN_types,
        typename PartitionPacksBySize<N, P<KeptPacks..., typename Append<First, KeptPacks>::type...>>::sizeN_types> {};

template <int, typename> struct NSubsets;

template <int N, template <typename...> class P, typename...Types>
struct NSubsets<N, P<Types...>> : NSubsetsHelper<N, P<Types...>, P<P<>>, P<>> {};

// -----------------------------------------------------------------------------------------------------------------------------------------------
// Testing

template <typename...> struct Pack {};

int main() {
    std::cout << std::boolalpha << std::is_same< NSubsets<2, Pack<int, char, double>>::type,
        Pack<Pack<int, char>, Pack<int, double>, Pack<char, double>>
    >::value << std::endl;  // false (darn!)
}

我在纸上描述了上面的包应该是输出,当我改变包的顺序时,它仍然是错误的。但是,正如我上面提到的,无论如何,这种方法还是比较差的。有关更好方法的任何建议吗?

更新:我发现了我的错误,并取代了

typename PartitionPacksBySize<N, P<KeptPacks..., typename Append<First, KeptPacks>::type...>>::sizeN_types>

typename Merge<P<SizeNPacks...>, typename PartitionPacksBySize<N, P<KeptPacks..., typename Append<First, KeptPacks>::type...>>::sizeN_types>::type

但是,你还能看到当N很大时,我的算法在最后N次迭代中是如何浪费时间的吗?

2 个答案:

答案 0 :(得分:2)

我们可以从一开始就精确生成大小为k的子集 - 这样会更有效率,因为我们只需要O(n^k)工作而不是O(2^n)工作。这里的算法只是迭代具有n 1个k位的Pack个字的所有排列,并为每个单词添加适当的constexpr int ctz(size_t n) { return n & 1 ? 0 : 1 + ctz(n >> 1); } constexpr size_t next_perm_impl(size_t v, size_t t) { return (t + 1) | (((~t & -~t) - 1) >> (ctz(v) + 1)); } constexpr size_t next_perm(size_t v) { return next_perm_impl(v, v | (v - 1)); }

我们先用bithack开始以constexpr形式找到next permutation

template <class... T> struct Pack { using type = Pack; };

template <size_t size, class result, class>
struct accumulator : result { };

template <size_t j, class... R, class T1, class... T>
struct accumulator<j, Pack<R...>, Pack<T1, T...>>
: accumulator<(j>>1), typename std::conditional<j&1, Pack<R..., T1>, Pack<R...>>::type, Pack<T...>>
{};

接下来我抓住了Columbo的累加器,他因为某些原因删除了你上一个问题的答案:

j

给定值Pack,我们可以确定与这些元素相关联的(1 << k) - 1。现在我们只需要从(1 << N) + (1 << (k-1)) - 1迭代到template <typename P, typename Result, size_t CUR, size_t LAST> struct PowerPackImpl; template <typename P, typename... R, size_t CUR, size_t LAST> struct PowerPackImpl<P, Pack<R...>, CUR, LAST> : PowerPackImpl<P, Pack<R..., typename accumulator<CUR, Pack<>, P>::type>, next_perm(CUR), LAST> { }; template <typename P, typename... R, size_t LAST> struct PowerPackImpl<P, Pack<R...>, LAST, LAST> : Pack<R...> { }; template <typename P, size_t K> struct PowerPack; template <typename... P, size_t K> struct PowerPack<Pack<P...>, K> : PowerPackImpl<Pack<P...>, Pack<>, (1 << K) - 1, (1 << sizeof...(P)) + (1 << (K-1)) - 1> { }; 。这可能是一种更有效的方法,但以下工作:

static_assert(std::is_same<
    typename PowerPack<Pack<int, char, double, float>, 1>::type,
    Pack<Pack<int>, Pack<char>, Pack<double>, Pack<float>>
>::value, "1 works");

static_assert(std::is_same<
    typename PowerPack<Pack<int, char, double, float>, 2>::type,
    Pack<Pack<int, char>, Pack<int, double>, Pack<char, double>, Pack<int, float>, Pack<char, float>, Pack<double, float> >
>::value, "2 works");

例如:

{{1}}

答案 1 :(得分:1)

这是基于PowerPack解决方案,但是过长条目的过滤在每个步骤中都会发生,因此比最后过滤更有效。

第1步:NAppend<N,Pack<...>,T>只会在T之后将新类型Pack<...>附加到N,如果之后的条目不超过template<std::size_t,typename,typename> struct NAppend { using type = void; }; template<std::size_t N,typename...Ts,typename T> struct NAppend<N,Pack<Ts...>,T> { using type = typename std::conditional< sizeof...(Ts)==N, void, Pack<Ts...,T> >::type; };

ShrinkPack

第2步:voidPackPackvoid中删除template<typename,typename U=Pack<>> struct ShrinkPack { using type = U; }; template<typename T,typename...Ts,typename...Us> struct ShrinkPack<Pack<T,Ts...>,Pack<Us...>> : std::conditional< std::is_void<T>::value, ShrinkPack<Pack<Ts...>,Pack<Us...>>, ShrinkPack<Pack<Ts...>,Pack<Us...,T>> >::type { };

NPack

第3步:template<std::size_t,typename,typename U=Pack<>> struct NPack { using type = U; }; template<std::size_t N,typename...Ts,typename...Us,typename...Vs> struct NPack<N,Pack<Pack<Ts...>,Us...>,Pack<Vs...>> : std::conditional< sizeof...(Ts)==N, NPack<N,Pack<Us...>,Pack<Vs...,Pack<Ts...>>>, NPack<N,Pack<Us...>,Pack<Vs...>> >::type { }; 过滤掉所有的条目。

PowerPack

最后一步:适应性扩展,类似于ShrinkPack并在每个扩展步骤应用NPack,并在结束时应用template<std::size_t N,typename,typename T=Pack<Pack<>>> struct NPowerPack { using type = typename NPack<N,T>::type; }; template<std::size_t N,typename T,typename...Ts,typename...Us> struct NPowerPack<N,Pack<T,Ts...>,Pack<Us...>> : NPowerPack<N,Pack<Ts...>,typename ShrinkPack<Pack<Us...,typename NAppend<N,Us,T>::type...>>::type> { };

static_assert(std::is_same<
    NPowerPack<1,Pack<int, char, double>>::type,
    Pack<Pack<int>, Pack<char>, Pack<double>>
>(), "");

static_assert(std::is_same<
    NPowerPack<2,Pack<int, char, double>>::type,
    Pack<Pack<int, char>, Pack<int, double>, Pack<char, double>>
>(), "");

试验:

{{1}}

Live example