Python- Eratosthenes-紧凑型Python的筛选

时间:2011-07-14 00:45:25

标签: python algorithm performance primes

这是我使用Sierat of Eratosthenes找到素数的代码。

list = [i for i in range(2, int(raw_input("Compute primes up to what number? "))+1)]  

for i in list:
    for a in list:
            if a!=i and a%i == 0:
                list.remove(a)

试图找到一种方法将那些嵌套for循环压缩成某种生成器或理解,但似乎你不能使用理解将函数应用于列表。我尝试使用地图和过滤器,但我似乎无法做到正确。

考虑这样的事情:

print map(list.remove(a), filter(lambda a, i: (a%i ==0 and a!=i), [(a, i) for i in list for a in list])

显然不会有十几个原因。如果我只是使用该代码的过滤器部分:

filter(lambda a, i: (a%i ==0 and a!=i), **[(a, i) for i in list for a in list]**

将两个变量放入lambda的正确方法是什么? (a,i)使它成为一个元组,但我想提交'a'和'i'作为自变量放入lambda。

我最终解决了这个单线问题:

print sorted(set([i for i in range(2, int(raw_input("Compute primes up to what number? "))+1)]).difference(a for i in l for a in l if a!=i and a%i == 0))

8 个答案:

答案 0 :(得分:15)

首先要注意的是,你所写的不是eratosthenes的筛子。看看有多少循环是一个完全天真的eratosthenes筛子执行:

def sieve1(n):
    loops = 0
    numbers = set(range(2, n))
    for i in range(2, int(n ** 0.5) + 1):
        for j in range(i * 2, n, i):
            numbers.discard(j)
            loops += 1
    return sorted(numbers), loops

测试:

>>> sieve1(100)
([2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 
  43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97], 
 178)

包含100个数字的178个循环(不包括排序)。只需稍作修改即可改善这一点:

def sieve2(n):
    loops = 0
    numbers = range(0, n)
    for prime in numbers:
        if prime < 2:
            continue
        elif prime > n ** 0.5:
            break
        for i in range(prime ** 2, n, prime):
            numbers[i] = 0
            loops += 1
    return [x for x in numbers if x > 1], loops

测试:

>>> sieve2(100)
([2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 
  43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97], 
 102)

100个数字的102个循环(不包括末尾的过滤器)。现在看看你的:

def sieve3(n):
    loops = 0
    numbers = range(2, n)
    for i in numbers:
        for j in numbers:
            if j != i and j % i == 0:
                numbers.remove(j)
            loops += 1
    return numbers, loops

测试:

>>> sieve3(100)
([2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 
  43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97], 
 663)

情况变得更糟:

>>> [sieve1(x)[1] for x in [100, 1000, 10000]]
[178, 2978, 41723]
>>> [sieve2(x)[1] for x in [100, 1000, 10000]]
[102, 1409, 16979]
>>> [sieve3(x)[1] for x in [100, 1000, 10000]]
[663, 28986, 1523699]

n = 10000,您的实施工作几乎可以完成100倍!

我的建议是在使其“紧凑”之前创建合理的实施方案。代码高尔夫可能很有趣,但无论长度如何,它都远不如编写高效代码那样具有挑战性或启发性。

那就是说,考虑这个单行(如果你不计算导入,你可以用lambda x, y: x - y代替operator.sub来除去导入。这实现了第一个算法,但改进很小:

>>> from operator import sub
>>> reduce(sub, (set(range(x ** 2, 100, x)) for x in range(2, int(100 ** 0.5) + 1)), set(range(2, 100)))
set([2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97])

答案 1 :(得分:5)

这不完全是你的循环的直接翻译,但它非常紧密和紧凑:

>>> l = range(2, 101)
>>> sorted(set(l).difference(a for i in l for a in l if a!=i and a%i == 0))
[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97]

虽然我建议a > i而不是a != 0更短更快;)

答案 2 :(得分:3)

你没有做过Eratosthenes的筛子;不正确实施算法的危险在于它会非常慢。例如,在10**6上尝试使用您的算法。

Eratosthenes有限筛子的最短实施我可以提出:

def primes(upTo):
    isPrime = list(range(upTo))
    for p in range(2,int(upTo**0.5)+1): #p: 2,3,4,...,sqrt(N)
        print(p, isPrime[p])
        if isPrime[p]:
            for multiple in range(p**2,upTo,p): #mult: p^2, p^2+p, p^2+2p, ..., N
                isPrime[multiple] = False
    return [x for x in isPrime[2:] if x]

演示:

>>> list(primes(29))
[2, 3, 5, 7, 11, 13, 17, 19, 23]

如果你忽略了换行符和大量的跳过偶数数字优化,它实际上相当简洁:

isPrime=[True]*upTo for p in range(2,upTo): if isPrime[p]: yield p for m in range(p,upTo,p): isPrime[m]=False

答案 3 :(得分:0)

以下单行与您的代码完全无关:

def primes(n):
    return set(range(2,n))-{c for i in range(2,n) for c in range(2*i,n,i)}

就像你的代码一样,这仍然是并非真正 Eratosthenes的筛子,因为,例如,它会徒劳地试图越过69等的倍数。然而,对于小于一百万或更多的值,它仍然比大多数其他Sieve相似的运行速度明显更快,因为对于小N,存在与“非素数”一样多的“素数”(数字的分数&lt; N是素数) 1/log(N))。

从源代码进行了大量修改,可能效率低于原始版本:http://codeblog.dhananjaynene.com/2011/06/10-python-one-liners-to-impress-your-friends/

答案 4 :(得分:0)

以下是筛子的简单演示。请注意,lambda不用作过滤函数,因为素数需要在定义时绑定。同样令人感兴趣的是它在不重复划分的意义上是有效的,但从长远来看它可能会导致you-know-what

import itertools

def primes():
    ints = itertools.count(2)
    while True:
        p = next(ints)
        yield p
        ints = itertools.ifilter(p.__rmod__, ints)

print list(itertools.islice(primes(), 10))
# [2, 3, 5, 7, 11, 13, 17, 19, 23, 29]

答案 5 :(得分:0)

def sieve(n):
    sieve_list = range(n)
    zero_list = [0] * n
    for i in range(2, int(n**.5) + 1):
        if sieve_list[i]:
            sieve_list[2*i:n:i] = zero_list[2*i:n:i]
    return filter(None, sieve_list)[1:]

答案 6 :(得分:0)

这是迄今为止我提出的最紧凑的真正筛子。这表现得非常好。

def pgen(n): # Sieve of Eratosthenes generator
    np = set() # keeps track of composite (not prime) numbers
    for q in xrange(2, n+1):
        if q not in np:
            yield q
            np.update(range(q*q, n+1, q))

>>> list(pgen(100))
[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73,
79, 83, 89, 97]            

这个稍微复杂的版本是我见过的最快的版本:

def pgen(n): # Sieve of Eratosthenes generator by Dan Salmonsen
    yield 2
    np = set()
    for q in xrange(3, n+1, 2):
        if q not in np:
            yield q
            np.update(range(q*q, n+1, q+q))            

这是一个真正的筛子作为列表理解:

def primes(n):
    sieve = set(sum([range(q*q, n+1, q+q) for q in odds], []))
    return [2] + [p for p in odds if p not in sieve]

>>> primes(100)
[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73,
79, 83, 89, 97]            

答案 7 :(得分:0)

并不是最紧凑的解决方案,但是Python3中range函数中的step参数在这里有帮助-

prime_sieve = [True] * (int(input('Primes Upto ?'))+1)
# The first prime number
for i in range(2, len(prime_sieve)):
    if prime_sieve[i]:
        for j in range(i+i, len(prime_sieve), i):
            prime_sieve[j] = False
        print(i, end=',')