这个主要功能真的有效吗?

时间:2012-12-06 16:18:19

标签: python algorithm math python-2.7 primes

由于我开始掌握Python,我开始在projecteuler.net上测试我新获得的Python技能。

无论如何,在某些时候,我最终制作了一个函数,用于获取所有素数的列表直到数字'n'。

以下是该函数的外观:

def primes(n):
    """Returns list of all the primes up until the number n."""

    # Gather all potential primes in a list.
    primes = range(2, n + 1)
    # The first potential prime in the list should be two.
    assert primes[0] == 2
    # The last potential prime in the list should be n.
    assert primes[-1] == n

    # 'p' will be the index of the current confirmed prime.
    p = 0
    # As long as 'p' is within the bounds of the list:
    while p < len(primes):
        # Set the candidate index 'c' to start right after 'p'.
        c = p + 1
        # As long as 'c' is within the bounds of the list:
        while c < len(primes):
            # Check if the candidate is divisible by the prime.
            if(primes[c] % primes[p] == 0):
                # If it is, it isn't a prime, and should be removed.
                primes.pop(c)
            # Move on to the next candidate and redo the process.
            c = c + 1
        # The next integer in the list should now be a prime, 
        # since it is not divisible by any of the primes before it. 
        # Thus we can move on to the next prime and redo the process.
        p = p + 1       
    # The list should now only contain primes, and can thus be returned.
    return primes

似乎工作得很好,虽然有一件事困扰着我。 在评论代码时,这件作品突然出现了:

# Check if the candidate is divisible by the prime.
if(primes[c] % primes[p] == 0):
    # If it is, it isn't a prime, and should be removed from the list.
    primes.pop(c)
# Move on to the next candidate and redo the process.
c += 1

如果候选人不能被素数整除,我们会检查位于'c + 1'的下一个候选人。没问题。

但是,如果候选人IS可以被素数整除,我们首先弹出它,然后检查位于'c + 1'的下一个候选者。 让我感到震惊的是,下一个候选人在弹出后不是位于'c + 1',而是'c',因为在'c'弹出后,下一个候选人“落入”该指数。

然后我认为该块应如下所示:

# If the candidate is divisible by the prime:
if(primes[c] % primes[p] == 0):
    # If it is, it isn't a prime, and should be removed from the list.
    primes.pop(c)
# If not:
else:
    # Move on to the next candidate.
    c += 1

上面的这个块对我来说似乎更正确,但让我想知道为什么原始作品看起来很好用。

所以,这是我的问题:

在弹出不是素数的候选人之后,我们可以假设,正如我的原始代码中那样,下一个候选人不能被同一个素数整除吗?

如果是这样,为什么会这样?

建议的“安全”代码是否会对那些在“不安全”代码中跳过的候选人进行不必要的检查?

PS:

我尝试将上述假设作为“不安全”函数的断言,并用n = 100000进行测试。没有出现问题。这是修改过的块:

# If the candidate is divisible by the prime:
if(primes[c] % primes[p] == 0):
    # If it is, it isn't a prime, and should be removed.
    primes.pop(c)
    # If c is still within the bounds of the list:
    if c < len(primes):
        # We assume that the new candidate at 'c' is not divisible by the prime.
        assert primes[c] % primes[p] != 0
# Move on to the next candidate and redo the process.
c = c + 1

6 个答案:

答案 0 :(得分:11)

它失败了更多的数字。第一个素数是 71 ,因为候选人可能会失败。 71的最小失败候选人是 10986448536829734695346889 ,这使得数字10986448536829734695346889 + 142蒙上阴影。

def primes(n, skip_range=None):
    """Modified "primes" with the original assertion from P.S. of the question.
    with skipping of an unimportant huge range.
    >>> primes(71)
    [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71]
    >>> # The smallest failing number for the first failing prime 71:
    >>> big_n = 10986448536829734695346889
    >>> primes(big_n + 2 * 71, (72, big_n))
    Traceback (most recent call last):
    AssertionError
    """
    if not skip_range:
        primes = list(range(2, n + 1))
    else:
        primes = list(range(2, skip_range[0]))
        primes.extend(range(skip_range[1], n + 1))
    p = 0
    while p < len(primes):
        c = p + 1
        while c < len(primes):
            if(primes[c] % primes[p] == 0):
                primes.pop(c)
                if c < len(primes):
                    assert primes[c] % primes[p] != 0
            c = c + 1
        p = p + 1
    return primes

# Verify that it can fail.
aprime = 71   # the first problematic prime 
FIRST_BAD_NUMBERS = (
        10986448536829734695346889, 11078434793489708690791399,
        12367063025234804812185529, 20329913969650068499781719,
        30697401499184410328653969, 35961932865481861481238649,
        40008133490686471804514089, 41414505712084173826517629,
        49440212368558553144898949, 52201441345368693378576229)

for bad_number in FIRST_BAD_NUMBERS:
    try:
        primes(bad_number + 2 * aprime, (aprime + 1, bad_number))
        raise Exception('The number {} should fail'.format(bad_number))
    except AssertionError:
        print('{} OK. It fails as is expected'.format(bad_number))

我通过搜索n个模数小素数的可能余数,通过像拼图这样的复杂算法解决了这些数字。最后一个简单的步骤是获得完整的n(通过三行Python代码中的中文余数定理)。我知道所有120个小于primorial(71)= 2 * 3 * 5 * 7 * 11 * 13 * 17 * 19 * 23 * 29 * 31 * 37 * 41 * 43 * 47 * 53 * 59 * 61 * 67 * 71的基本解决方案会定期重复此数字的所有倍数。我为每十年测试过的素数多次重写了算法,因为每十年解决方案的速度比以前慢很多。也许我在可接受的时间内找到一个较小的解决方案,使用相同的算法73或79。


编辑:

我想找到一个完整的无声失败的不安全的原始函数。也许存在一些由不同素数组成的候选人。这种解决方案只能推迟最终结果。时间和资源的每一步都会变得越来越昂贵。因此,只有由一个或两个素数组成的数字才具有吸引力。

我希望隐藏的候选人 c 只有两个解决方案是好的:c = p ** nc = p1 * p ** nc = p1 ** n1 * p ** n其中 p p1 是素数, n 是一个大于1的幂。 primes 函数失败,如果c - 2 * p可被整除,则小于0 p 如果 c-2n c 之间的所有数字都可以被小于 p 的任何素数整除。变量p1 * p ** n还要求 p1 p1&lt; p )之前相同的 c 失败,正如我们已经知道的那样无数的这样的候选人。

编辑:我发现一个较小的示例失败:编号为121093190175715194562061为素数79.(比71少了约九十倍)我可以&#39;继续使用相同的算法来查找较小的示例,因为我的笔记本电脑上的所有702612基本解决方案花费了超过30小时的时间。

我还对所有小于400000000(4E10)的候选人以及所有相关的素数进行了验证,没有候选人会在问题中失败。直到你有数TB的内存和数千年的时间,算法中的断言才会通过,因为你的时间复杂度是O((n / log(n))^ 2)或非常相似。

答案 1 :(得分:4)

你的观察似乎是准确的,这是一个很好的捕获。

我怀疑它起作用的原因,至少在某些情况下,是因为复合数字实际上被分解为多个素数。因此,内部循环可能会错过第一个因子的值,但随后它会在后来的因子中获取它。

对于一个小的“n”,您可以打印出列表的值,看看是否正在发生这种情况。

顺便提一下,这种寻找素数的方法是基于Eratothenes的筛选。在筛选时,如果“c”是“p”的倍数,那么下一个值永远不会是同一个素数的倍数。

问题是:在任何情况下,p * x和p *(x + 1)之间的所有值都可以被一些小于p和p * x + 1的素数整除。 (这是算法会遗漏一个值的地方,以后不会被捕获。)但是,其中一个值是偶数,所以它将在圆“2”上消除。因此,真正的问题是,是否存在p * x和p *(x + 2)之间的所有值都可以被小于p的数字整除的情况。

副手,我想不到满足这个条件的任何小于100的数字。对于p = 5,总有一个值在两个连续的5的倍数之间不能被2或3整除。

似乎有很多关于素数空位和序列的文章,但不是那么多关于连续整数的序列,可以被小于p的数字整除。经过一些(好的,很多)试验和错误之后,我已经确定39,474(17 * 2,322)和39,491(17 * 2,233)之间的每个数字都可以被小于17的整数整除:

39,475  5
39,476  2
39,477  3
39,478  2
39,479  11
39,480  2
39,481  13
39,482  2
39,483  3
39,484  2
39,485  5
39,486  2
39,487  7
39,488  2
39,489  3
39,490  2

我不确定这是否是第一个这样的值。但是,我们必须找到两倍于此的序列。我认为这不太可能,但不确定是否有证据。

我的结论是原始代码可能有效,但您的修复是正确的。没有证据证明没有这样的序列,它看起来像一个bug,尽管这个bug可能是非常非常非常罕见的。

答案 2 :(得分:1)

  • 在可能的素数的连续序列中给出两个数n,m,使得n和m不能被最后的除数p整除,则m-n <1。 P
  • 给定q(下一个更高的除数)&gt; p,那么如果n可被q整除,那么可被q整除的下一个数是n + q> n + p>米 所以m应该在当前迭代中跳过以进行可分性测试

    Here n = primes[c] 
    
    m = primes[c + 1], i.e. primes[c] after primes.pop(c)
    
    p = primes[p]
    q = primes[p+1] 
    

答案 3 :(得分:0)

这是一个想法:

Triptych解释 1 c之后的下一个数字不能是c + p,但我们仍然需要证明它也永远不会是c + 2p。

如果我们使用primes = [2],我们只能有一个连续的“非素数”,一个可被2整除的数字。

如果我们使用primes = [2,3]我们可以构造3个连续的“非素数”,数字除以2,数字除以3,数字除以2,它们不能得到下一个数字。或

2,3,4 =&gt;连续3次“非素数”

尽管2和3不是“非素数”,但我更容易根据这些数字进行思考。

如果我们使用[2,3,5],我们会得到

2,3,4,5,6 =&gt; 5连续“非素数”

如果我们使用[2,3,5,7],我们会得到

2,3,4,5,6,7,8,9,10 =&gt; 9连续“非素数”

出现了这种模式。我们可以得到的最连续的非素数是下一个素数 - 2。

因此,如果next_prime&lt; p * 2 + 1,我们必须在c和c + 2p之间至少有一些数字,因为在给定素数的情况下,连续非素数的数量不够长。

我不知道非常大的数字,但我认为next_prime < p * 2 + 1可能会有非常大的数字。

我希望这是有道理的,并增加一些亮点。


1 Triptych的答案已被删除。

答案 4 :(得分:0)

这并没有提供一个远程结论性答案,但这是我在此尝试的内容:

我在这里重申了所需的假设(lpf代表最低素数因子):

For any composite number, x, where:
    lpf(x) = n
There exists a value, m, where 0 < m < 2n and:
    lpf(x+m) > n

可以很容易地证明x的值存在于不存在复合数(x + m)以满足不等式的情况。任何平方素数表明:

lpf(x) = x^.5, so x = n^2
n^2 + 2n < (n + 1)^2 = n^2 + 2n + 1

因此,在任何平方素数的情况下,为了使其成立,必须有一个素数p,在x

我认为可以得出结论,正方形的渐近分布(x ^ .5)与素数定理(素数的渐近分布约为x /(ln x))相比,但是,实际上,我的理解素数定理充其量是有限的。

我没有任何策略可以将这个结论扩展到非方形复合数字,所以这可能不是一个有用的途径。

我使用上面重述的问题整理了一个程序测试值。

直接测试此语句应该只删除运行所述算法的任何幸运结果。通过获得幸运的结果,我指的是被跳过的值可能不安全,但是由于跳过的值不能被当前正在迭代的数字整除,或者被删除,因此不会出现任何不正确的结果通过后续迭代获得。本质上,如果算法得到正确的结果,但要么没有找到每个消除值的最小素数因子,要么没有严格检查每个素数结果,我对它不满意。如果存在这样的情况,我认为有理由认为案件也存在不幸运的地方(尽管它们可能不常见),并且会产生不正确的结果。

然而,运行我的测试显示在2到2,000,000的值中没有反例。因此,对于它的价值,除非我的逻辑不正确,否则所述算法的值应至少是2,000,000,除非我的逻辑不正确。

这就是我必须添加的内容。很棒的问题,Phazyck,玩得很开心!

答案 5 :(得分:-2)

如果素数p除了候选者c,则可被p整除的下一个较大候选者是c + p。因此,您的原始代码是正确的。

然而,制作素数列表是一种腐烂的方式;尝试使用n = 1000000,看看它有多慢。问题是,当您使用筛子时,您正在进行试验分割。这是一个简单的筛子(伪代码,我会让你翻译成Python或其他语言):

function primes(n)
    sieve := makeArray(2..n, True)
    for p from 2 to n step 1
        if sieve[p]
            output p
            for i from p+p to n step p
                sieve[i] := False

这应该会在不到一秒的时间内获得不到一百万的素数。还有其他筛选算法甚至更快。

这种算法被称为Eratosthenes的筛子,大约在2200年前由希腊数学家发明。 Eratosthenes是一个有趣的家伙:除了筛选素数之外,他还发明了闰日和纬度和经度系统,准确地计算了从太阳到地球和地球周长的距离,并且暂时是托勒密图书馆的首席图书馆馆长。在亚历山大。

当您准备好了解有关素数编程的更多信息时,我在我的博客上谦虚地推荐这个essay