在编译时生成素数

H-0*_*005 8 c++ primes metaprogramming compile-time

我对如何在编译时生成素数数组很感兴趣(我相信唯一的方法是使用元编程(在 C++ 中,不确定它在其他语言中是如何工作的))。

快速说明,我不想只说int primes[x] = {2, 3, 5, 7, 11, ...};,因为我想在竞争性编程中使用这种方法,其中源文件不能大于10KB。所以这排除了任何超过几千个元素的预生成数组。

例如,我知道您可以在编译时生成斐波那契数列,但这相当容易,因为您只需添加最后 2 个元素。对于素数,我真的不知道如何在没有循环的情况下做到这一点(我相信这是可能的,但我不知道如何,我猜是使用递归),而且我不知道如何在编译时评估循环-时间。

所以我正在寻找一个关于如何解决这个问题的想法(至少),甚至可能是一个简短的例子

for*_*818 6

以下只是给你一些开始。它严重依赖递归实例化类型,这不是很有效,我不想在实现的下一次迭代中看到。

divxiff的除数x%div == false

template <int div,int x>
struct is_divisor_of : std::conditional< x%div, std::false_type, std::true_type>::type {};
Run Code Online (Sandbox Code Playgroud)

一个数x不是质数,如果有一个p < x是 的除数x

template <int x,int p=x-2>
struct has_divisor : std::conditional< is_divisor_of<p,x>::value, std::true_type, has_divisor<x,p-1>>::type {};
Run Code Online (Sandbox Code Playgroud)

如果没有1 < p < x分歧xx没有除数(因此是素数):

template <int x>
struct has_divisor<x,1> : std::false_type {};
Run Code Online (Sandbox Code Playgroud)

Amain来测试一下:

int main()
{
    std::cout << is_divisor_of<3,12>::value;
    std::cout << is_divisor_of<5,12>::value;
    std::cout << has_divisor<12>::value;
    std::cout << has_divisor<13>::value;
}
Run Code Online (Sandbox Code Playgroud)

输出:

1010
Run Code Online (Sandbox Code Playgroud)

现场演示

PS:您可能最好按照constexpr评论中的建议采用功能路线。上面的内容与计算斐波那契数的递归模板一样有用(即,除了用于演示之外,并没有真正有用;)。

  • 啊哈哈,抱歉,我太神秘了。我指的是“int constexpr prime_numbers = 你看到我的用户名吗?”之类的东西。我显然是在开玩笑:P (2认同)
  • @Enlico 啊好吧,我对 463035818 最大的研究几乎完成了,接下来我将集中精力证明它是唯一的素数。完成后,我将相应地更新答案。 (2认同)
  • 我很确定,有了“正确的”[环](https://en.wikipedia.org/wiki/Algebra_over_a_field#Algebras_and_rings),它是可以证明的:-) (2认同)

Arm*_*gny 6

我们可以对一些素数进行编译时预计算,并将它们放入编译时生成的数组中。然后使用简单的查找机制来获取值。这仅适用于少量素数。但它应该向您展示基本机制。

我们将首先定义一些用于计算素数作为constexpr函数的默认方法:

constexpr bool isPrime(size_t n) noexcept {
    if (n <= 1) return false;
    for (size_t i = 2; i*i < n; i++)    if (n % i == 0) return false;
    return true;
}
constexpr unsigned int primeAtIndex(size_t i) noexcept {
    size_t k{3};
    for  (size_t counter{}; counter < i; ++k)
        if (isPrime(k)) ++counter;
    return k-1;
}
Run Code Online (Sandbox Code Playgroud)

这样,可以在编译时轻松计算素数。然后,我们std::array用所有素数填充 a 。我们还使用了一个constexpr函数,并使其成为带有可变参数包的模板。

我们用来std::index_sequence为索引 0,1,2,3,4,5, .... 创建一个质数。

这很直接,并不复杂:

// Some helper to create a constexpr std::array initilized by a generator function
template <typename Generator, size_t ... Indices>
constexpr auto generateArrayHelper(Generator generator, std::index_sequence<Indices...>) {
    return std::array<decltype(std::declval<Generator>()(size_t{})), sizeof...(Indices) > { generator(Indices)... };
}
Run Code Online (Sandbox Code Playgroud)

该函数将输入一个索引序列 0,1,2,3,4,... 和一个生成器函数,并返回std::array<return type of generator function, ...>由生成器计算出的带有相应数字的a 。

我们创建了一个 next 函数,它将使用索引序列 1,2,3,4,...Max 调用上面的函数,如下所示:

template <size_t Size, typename Generator>
constexpr auto generateArray(Generator generator) {
    return  generateArrayHelper(generator, std::make_index_sequence<Size>());
}
Run Code Online (Sandbox Code Playgroud)

而现在,终于,

constexpr auto Primes = generateArray<100>(primeAtIndex);
Run Code Online (Sandbox Code Playgroud)

会给我们一个std::array<unsigned int, 100>名为 Primes的编译时,包含所有 100 个素数。如果我们需要第 i 个素数,那么我们可以简单地写Primes [i]. 运行时不会有计算。

我认为没有更快的方法来计算第 n 个素数。

请参阅下面的完整程序:

#include <iostream>
#include <utility>
#include <array>

// All done during compile time -------------------------------------------------------------------
constexpr bool isPrime(size_t n) noexcept {
    if (n <= 1) return false;
    for (size_t i = 2; i*i < n; i++)    if (n % i == 0) return false;
    return true;
}
constexpr unsigned int primeAtIndex(size_t i) noexcept {
    size_t k{3};
    for  (size_t counter{}; counter < i; ++k)
        if (isPrime(k)) ++counter;
    return k-1;
}
// Some helper to create a constexpr std::array initilized by a generator function
template <typename Generator, size_t ... Indices>
constexpr auto generateArrayHelper(Generator generator, std::index_sequence<Indices...>) {
    return std::array<decltype(std::declval<Generator>()(size_t{})), sizeof...(Indices) > { generator(Indices)... };
}
template <size_t Size, typename Generator>
constexpr auto generateArray(Generator generator) {
    return  generateArrayHelper(generator, std::make_index_sequence<Size>());
}

// This is the definition of a std::array<unsigned int, 100> with prime numbers in it
constexpr auto Primes = generateArray<100>(primeAtIndex);
// End of: All done during compile time -----------------------------------------------------------


// Some debug test driver code
int main() {
    for (const auto p : Primes) std::cout << p << ' '; std::cout << '\n';
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

顺便一提。该generateArrayfucntionality当然会还与其他发电机职能的工作。

如果您需要例如三角形数字,那么您可以使用:

constexpr size_t getTriangleNumber(size_t row) noexcept {
    size_t sum{};
    for (size_t i{ 1u }; i <= row; i++) sum += i;
    return sum;
}
Run Code Online (Sandbox Code Playgroud)

constexpr auto TriangleNumber = generateArray<100>(getTriangleNumber);
Run Code Online (Sandbox Code Playgroud)

会给你一个constexpr std::array<size_t, 100>用三角形数计算的编译时间。

对于斐波那契数字,您可以使用

constexpr unsigned long long getFibonacciNumber(size_t index) noexcept {
    unsigned long long f1{ 0ull }, f2{ 1ull }, f3{};
    while (index--) { f3 = f2 + f1; f1 = f2; f2 = f3; }
    return f2;
}
Run Code Online (Sandbox Code Playgroud)

constexpr auto FibonacciNumber = generateArray<93>(getFibonacciNumber);
Run Code Online (Sandbox Code Playgroud)

获取适合 64 位值的所有斐波那契数。

所以,一个相当灵活的帮手。


警告

大数组大小会导致编译器出现堆外错误。


使用 Microsoft Visual Studio Community 2019 版本 16.8.2 进行开发和测试。

另外使用 clang11.0 和 gcc10.2 编译和测试

语言:C++17