如何快速评估零集

How to evaluate zero sets fast?

本文关键字:零集 何快速 评估      更新时间:2023-10-16

这篇最近的代码高尔夫帖子询问了在C中快速实现以下内容的可能性(假设n是一个无符号整数):

if (n==6 || n==8 || n==10 || n==12 || n==14 || n==16 || n==18 || n==20)

一种可能的简化是观察到数字a[]={6,8,10,12,14,16,18,20}形成算术级数,因此移动范围,然后使用一些位技巧

if (((n - 6) & 14) + 6 == n)

正如John Bollinger所回答的那样,这会导致更短(而且可能更高效)的实现。

现在我在问类似优雅(希望同样高效)的实现是什么

if (n==3 || n==5 || n==11 || n==29 || n==83 || n==245 || n==731 || n==2189)

提示:这一次数字a[k]形成几何级数:a[k]=2+3^k

我想在一般情况下,对数字a[k]进行排序,然后进行对数搜索来测试n是否是排序数组的成员,这是最好的选择。

if ((n > 2) && (2187 % (n - 2) == 0))

检查(n - 2)是否是3的幂并且小于或等于2187(3到7的幂)

一般来说,要检查任何无符号整数n是否是素数k的幂,可以检查n是否除以可以存储在无符号整数中的k的最大幂。

这与识别三次方非常相似,例如,您可以调整此解决方案:

bool test(unsigned x) {
    x -= 2;
    if (x > 2187)
        return 0;
    if (x > 243)
        x *= 0xd2b3183b;
    return x <= 243 && ((x * 0x71c5) & 0x5145) == 0x5145;
}

在给定的范围内,这可以进一步简化(通过蛮力找到):

bool test2(unsigned x) {
  x -= 2;
  return x <= 2187 && (((x * 0x4be55) & 0xd2514105) == 5);
}

提示:这一次数字a[k]形成几何级数:a[k]=2+3^k

n = 2 + 3^k
n - 2 = 3^k
(n - 2) / 3^k = 1
(n - 2) % 3^k = 0
k = 0 ~ n-2 = 3^0 = 1, n = 3
k = 1 ~ n-2 = 3^1 = 3, n = 5
k = 2 ~ n-2 = 3^3 = 9, n = 11
if (n > 2 && isPow(n-2, 3))

具有函数isPow(x,y) 的定义

bool isPow(unsigned long x, unsigned int y)
{
    while (x % y == 0)
    {
        x /= y;
    }
    return x == 1;
}
n = n  ~ n-2 = 3^k/3 = 3^(k-1)/3 = .. = 3^1/3 = 1%3 != 0
n = 11 ~ n-2 = 9/3 = 3/3 = 1%3 != 0
n = 5  ~ n-2 = 3/3 = 1%3 != 0
n = 3  ~ n-2 = 1%3 != 0

类似地,我们可以扣除k。。

int k = findK(n-2, 3);
int findK(unsigned long x, unsigned int y)
{
    unsigned int k = 0;
    while (x % y == 0)
    {
        x /= y;
        k++;
    }
    if (x == 1) return k;
    else return (-1);
}
n - 2 = 3 * 3^(k-1)       for k > 0
(n - 2) / 3 = 3^(k-1)
(n - 2) / 3 / 3 = 3^(k-2)
(n - 2) / 3 / 3 / 3 = 3^(k-3)
(n - 2) / 3 / 3 / 3 / 3 = 3^(k-4)
..
(n - 2) / 3 / 3 / ..i times = 3^(k-i)
..
(n - 2) / 3 / 3 / ..k times = 3^0 = 1

在相关帖子中发现了类似的问题:您可以使用std::find

bool found = (std::find(my_var.begin(), my_var.end(), my_variable) != my_var.end());
// my_var is a list of elements.

(请确保包含<algorithm>)。

对于这种东西,在99%的情况下,有一个图书馆为你做这项工作。

做这类事情的一种非常有效的方法是使用集合,尤其是unordered_set。作为一个哈希表,搜索是平均常数,最差线性。它也比一系列的条件和规模要优雅得多。

只需插入要进行比较的值,并将count作为集合中的值。如果它被找到了,那就是你测试的价值之一。

std::unordered_set< int > values;
values.insert( 3 );
values.insert( 5 );
values.insert( 11 );
values.insert( 29 );
values.insert( 83 );
values.insert( 245 );
values.insert( 731 );
values.insert( 2189 );
...
if( values.count( input ) )
    std::cout << "Value is in set.n";
else
    std::cout << "Value is NOT in set.n";

以下是我的想法:

bool b;
if (n >= 3 && n <= 2189)
{
    double d = std::log(n - 2)/std::log(3); // log₃(n - 2)
    b = std::trunc(d) == d; // Checks to see if this is an integer
    // If ‘b’ then ‘n == a[log₃(n - 2)]’
}
else b = false;
if (b)
{
    // your code
}

由于数字n是一个小整数,浮点不准确应该不是问题。

当然,它不会像整数运算那样快,而且它不适合表达式,它可能比某种数组搜索更快,尤其是如果你在哪里增加最大n值。

EDIT我测试了我的代码(没有启用优化),平均而言(对于小于或等于2189的所有n),我的版本耗时185.849 ns,而||版本耗时116.546 ns,(多次运行我的程序会产生类似的结果),所以这并不快。同样由于一些奇怪的原因,它在n == 245时将b设置为false(这应该是真的)。