找到最低的胶合序列,可以提供超过65个素数

时间:2015-04-28 13:17:15

标签: python sequence primes memoization collatz

我有一个任务,我需要在Python中找到包含超过65个素数的最低Collat​​z序列。

例如,19的Collat​​z序列是:

  

19,58,29,88,44,22,11,34,17,52,26,13,40,20,10,5,16,8,   4,2,1

此序列包含7个素数。

我还需要使用memoization,因此它不必运行" year"找到它。我找到了Collat​​z序列的备忘录代码,但是当我只需要素数时,我无法弄清楚如何让它工作。

以下是我发现的Collat​​z记忆代码:

lookup = {}
def countTerms(n):
    if n not in lookup:
        if n == 1:
            lookup[n] = 1
        elif not n % 2:
            lookup[n] = countTerms(n / 2)[0] + 1
        else:
            lookup[n] = countTerms(n*3 + 1)[0] + 1

    return lookup[n], n

这是我的素数测试员:

def is_prime(a):
    for i in xrange(2,a):
        if a%i==0:
            #print a, " is not a prime number"
            return False
    if a==1:
        return False
    else:
        return True

2 个答案:

答案 0 :(得分:3)

您的现有代码缩进错误。我认为这项任务是一项家庭作业,所以我不会发布一个完整的工作解决方案,但我会给你一些有用的片段。

首先,这是一个稍微高效的素性测试仪。而不是测试是否所有小于a的数字都是a的因子,它只测试a的平方根。

def is_prime(a):
    for i in xrange(2, int(1 + a ** 0.5)):
        if a % i == 0:
            return False
    return True

请注意,此函数会为True返回a = 1。没关系,因为你不需要测试1:你可以将它预加载到lookup字典中:

lookup = {1:0}

您的countTerms函数需要稍微修改,以便在当前lookup为素数时仅向n计数添加一个。{1}}。在Python中,False的数值为0,True的数值为1.这在这里非常方便:

def count_prime_terms(n):
    ''' Count the number of primes terms in a Collatz sequence '''
    if n not in lookup:
        if n % 2:
            next_n = n * 3 + 1
        else:
            next_n = n // 2

        lookup[n] = count_prime_terms(next_n) + is_prime(n)
    return lookup[n]

我已将函数名称更改为Pythonic。

FWIW,第一个包含65个或更多素数的Collat​​z序列实际上包含67个素数。它的种子数超过180万,在检查所有序列到种子时需要进行素性测试的最高数是151629574372.完成后,lookup dict包含3920492个条目。

为了回应James Mills关于递归的评论,我编写了一个非递归版本,并且很容易看到迭代和递归版本都产生相同的结果我发布了一个完整的工作程序。我上面说过,我不打算这样做,但我认为现在应该可以这样做,因为spørreren已经使用我在原始答案中提供的信息编写了他们的程序。

我完全同意避免递归是好的,除非在适合问题域的情况下(例如,树遍历)。 Python不鼓励递归 - 它不能优化尾调用递归,并且它强加了递归深度限制(尽管如果需要可以修改该限制)。

这种Collat​​z序列素数计数算法是递归地自然陈述的,但迭代地做起来并不困难 - 我们只需要一个列表来暂时保持序列,同时确定其所有成员的素数。确实,这个列表占用了RAM,但它(可能)在空间方面比递归版本需要的堆栈帧要求更有效。

当解决OP中的问题时,递归版本的递归深度为343。这完全在默认限制范围内,但它仍然不好,如果你想搜索包含更多数量的素数的序列,你将达到这个限制。

迭代&递归版本以大致相同的速度运行(至少,它们在我的机器上运行)。为了解决OP中所述的问题,他们都需要2分钟的时间。这比我原来的解决方案要快得多,主要是因为素性测试的优化。

基本的Collat​​z序列生成步骤已经需要确定数字是奇数还是偶数。很明显,如果我们已经知道一个数字是偶数,那么就没有必要测试它是否是一个素数。 :)我们还可以消除is_prime函数中偶数因子的测试。我们可以通过简单地将2的结果加载到lookup缓存中来处理2是素数的事实。

在相关的说明中,当搜索包含给定数量的素数的第一个序列时,我们不需要测试以偶数开始的任何序列。偶数(除2之外)不会增加序列的素数,并且因为这样的序列中的第一个奇数将低于我们当前的数字,其结果将已经在lookup缓存中,假设我们从3开始搜索。如果我们不从3开始搜索,我们只需要确保我们的起始种子足够低,这样我们就不会意外地错过包含所需数量的素数的第一个序列。采用此策略不仅可以减少所需的时间,还可以减少查找缓存中的条目数。

#!/usr/bin/env python

''' Find the 1st Collatz sequence containing a given number of prime terms

    From http://stackoverflow.com/q/29920691/4014959

    Written by PM 2Ring 2015.04.29

    [Seed == 1805311, prime count == 67]
'''

import sys

def is_prime(a):
    ''' Test if odd `a` >= 3 is prime '''
    for i in xrange(3, int(1 + a ** 0.5), 2):
        if not a % i:
            return 0
    return 1


#Track the highest number generated so far; use a list
# so we don't have to declare it as global...
hi = [2]

#Cache for sequence prime counts. The key is the sequence seed,
# the value is the number of primes in that sequence.
lookup = {1:0, 2:1}

def count_prime_terms_iterative(n):
    ''' Count the number of primes terms in a Collatz sequence 
        Iterative version '''
    seq = []
    while n not in lookup:
        if n > hi[0]:
            hi[0] = n

        if n % 2:
            seq.append((n, is_prime(n)))
            n = n * 3 + 1
        else:
            seq.append((n, 0))
            n = n // 2

    count = lookup[n]
    for n, isprime in reversed(seq):
        count += isprime
        lookup[n] = count

    return count

def count_prime_terms_recursive(n):
    ''' Count the number of primes terms in a Collatz sequence
        Recursive version '''
    if n not in lookup:
        if n > hi[0]:
            hi[0] = n

        if n % 2:
            next_n = n * 3 + 1
            isprime = is_prime(n)
        else:
            next_n = n // 2
            isprime = 0
        lookup[n] = count_prime_terms(next_n) + isprime

    return lookup[n]


def find_seed(numprimes, start):
    ''' Find the seed of the 1st Collatz sequence containing
        `numprimes` primes, starting from odd seed `start` '''
    i = start
    mcount = 0

    print 'seed, prime count, highest term, dict size'
    while mcount < numprimes:
        count = count_prime_terms(i)
        if count > mcount:
            mcount = count
            print i, count, hi[0], len(lookup)
        i += 2


#count_prime_terms = count_prime_terms_recursive
count_prime_terms = count_prime_terms_iterative

def main():
    if len(sys.argv) > 1:
        numprimes = int(sys.argv[1])
    else:
        print 'Usage: %s numprimes [start]' % sys.argv[0]
        exit()

    start = int(sys.argv[2]) if len(sys.argv) > 2 else 3 

    #Round `start` up to the next odd number
    if start % 2 == 0:
        start += 1

    find_seed(numprimes, start)


if __name__ == '__main__':
    main()

使用

运行时

$ ./CollatzPrimes.py 65

输出

seed, prime count, highest term, dict size
3 3 16 8
7 6 52 18
19 7 160 35
27 25 9232 136
97 26 9232 230
171 28 9232 354
231 29 9232 459
487 30 39364 933
763 32 250504 1626
1071 36 250504 2197
4011 37 1276936 8009
6171 43 8153620 12297
10971 44 27114424 21969
17647 48 27114424 35232
47059 50 121012864 94058
99151 51 1570824736 198927
117511 52 2482111348 235686
202471 53 17202377752 405273
260847 55 17202377752 522704
481959 59 24648077896 966011
963919 61 56991483520 1929199
1564063 62 151629574372 3136009
1805311 67 151629574372 3619607

答案 1 :(得分:1)

让我们从一个确定数字是否为素数的函数开始;我们将使用Miller-Rabin算法,该算法比我们将要处理的数字大小的试验速度更快:

from random import randint

def isPrime(n, k=5): # miller-rabin
    if n < 2: return False
    for p in [2,3,5,7,11,13,17,19,23,29]:
        if n % p == 0: return n == p
    s, d = 0, n-1
    while d % 2 == 0:
        s, d = s+1, d/2
    for i in range(k):
        x = pow(randint(2, n-1), d, n)
        if x == 1 or x == n-1: continue
        for r in range(1, s):
            x = (x * x) % n
            if x == 1: return False
            if x == n-1: break
        else: return False
    return True

由于我们想要找到满足条件的第一个号码,我们的策略将是从2开始并逐步完成,随时存储结果。我们使用Collat​​z序列中从0到2的素数计算缓存(这是一个不好的双关语,对不起):

primeCount = [0,0,1]

函数pCount(n)计算 n 的Collat​​z序列中的素数。只要序列 k 的当前值低于 n ,我们就会在缓存中查找结果。在此之前,我们测试Collat​​z序列中的每个奇数是否具有素数,并在适当时增加素数 p 。当我们有 n 的素数时,我们将它添加到缓存中并返回它。

def pCount(n):
    k, p = n, 0
    while k > 0:
        if k < n:
            t = p + primeCount[k]
            primeCount.append(t)
            return t
        elif k % 2 == 0:
            k = k / 2
        elif isPrime(k):
            p = p + 1
            k = 3*k + 1
        else:
            k = 3*k + 1

现在只需计算每个 n 的素数,按顺序从3开始,在素数超过65时停止:

n = 3
t = pCount(n)
while t < 65:
    n = n + 1
    t = pCount(n)

这不会花很长时间,不到一分钟就在我的电脑上。结果如下:

print n

结果中有67个素数。如果你想看到它们,这里有一个简单的函数打印给定的 n 的Collat​​z序列:

def collatz(n):
    cs = []
    while n != 1:
        cs.append(n)
        n = 3*n+1 if n&1 else n//2
    cs.append(1)
    return cs

以下是素数列表:

filter(isPrime,collatz(n))

娱乐数学真是个有趣的问题!

编辑:由于人们询问了Miller-Rabin素性测试仪,让我展示一款基于2,3,5轮的简单素性测试仪。它通过2,3和5进行试验除以及不是2,3或5的倍数的数字,其中包括一些复合材料,因此试验除以素数的效率稍低,但没有必要预先计算并存储素数,因此使用起来更容易。

def isPrime(n): # 2,3,5-wheel
    if n < 2: return False
    wheel = [1,2,2,4,2,4,2,4,6,2,6]
    w, next = 2, 0
    while w * w <= n:
        if n % w == 0: return False
        w = w + wheel[next]
        next = next + 1
        if next > 10: next = 3
    return True

filter(isPrime,range(1000000))只需几秒钟就可以识别出不到一百万的78498素数。您可能希望比较此素性测试仪与Miller-Rabin测试仪的时序,并确定交叉点在运行时效率方面的位置;我没有做任何正式的时间,但似乎在与Miller-Rabin teseter同时解决65-collat​​z-prime问题。或者你可能想要将试验师与2,3,5轮组合到一定限度,比如1000或10000或25000,然后对幸存者运行Miller-Rabin;它可以快速消除大多数复合材料,因此可以非常快速地运行。