解释这个素数生成器函数,我无法理解[python]

时间:2011-06-08 04:26:05

标签: python math primes

def primes(n): 
    if n==2: return [2]
    elif n<2: return []
    s=range(3,n+1,2)
    mroot = n ** 0.5
    half=(n+1)/2-1
    i=0
    m=3
    while m <= mroot:
        if s[i]:
            j=(m*m-3)/2
            s[j]=0
            while j<half:
                s[j]=0
                j+=m
        i=i+1
        m=2*i+3
    return [2]+[x for x in s if x]

你好,我在http://code.activestate.com/recipes/366178-a-fast-prime-number-list-generator/发现了这个功能而且我被卡住了。我不明白这一点。我认为它使用素数的一些属性,但所有那些单字母变量都是如此神秘。你能否解释一下?

我明白了: mroot是您要检查素数的数字的限制 我知道该函数将列表s更改为0来标记倍数。我也理解最后的列表复制,我理解s。

但是为什么一半?什么是j?什么是米?

你能发一些评论吗?

3 个答案:

答案 0 :(得分:5)

逐行细分:

def primes(n): 
    if n==2: return [2]

此函数返回素数列表<= n。因此,如果n == 2,该列表仅包含2.足够简单。

    elif n<2: return []

在这里,2号以下没有素数,所以返回一个空列表。

    s=range(3,n+1,2)

所以s是一个从3开始并转到n + 1的奇数列表。 s代表筛子 - 这是一种筛分算法,这意味着复合(非素数)数字将从列表中筛选出来。实际上,他们将被“划掉”。有关筛分算法的详细说明,请参见下文。

    mroot = n ** 0.5

由于它是筛子,我们可以在达到n的平方根后停止算法。

    half=(n+1)/2-1

这是s长度的明确公式;它可以替换为len(s),但是对于大的n值计算可能需要更长的时间。这对于终止算法的某些部分也很有用。

    i=0
    m=3

我是我们的指数;我只需走过筛子,检查每个值。如果值为0,则该数字已被“划掉”,因为它不是素数。 m只是s[i]在任何特定时刻的价值;后一行保持更新。

    while m <= mroot:
        if s[i]:

由于s[i]评估为True,因此尚未从列表中删除。这意味着它是最好的!所以现在我们必须弄清楚列表中的哪些数字是s[i]的倍数 - 它们都是非素数,应该从列表中划掉。

            j=(m*m-3)/2
            s[j]=0

现在开始有趣了。因为筛子不是连续数字的列表,而是奇数列表,我们必须找出s中我们的素数的倍数。在这种情况下,我们的素数是3,所以我们需要找到9,15,21,27的索引...(我们不必找到6,12,18 ......因为它们'甚至,所以不在列表中)。这种用于查找索引的特定技术非常聪明,因为作者已经发现,一旦特定素数的所有倍数都被划掉,就可以跳过它们。这意味着我们的素数的第一个未划掉的多数实际上是该素数的平方。 (例如,如果素数为7,7 * 3 = 21且7 * 5 = 35已经被划掉,那么我们必须处理的7的第一个倍数是7 * 7.)感觉,很容易看出s中9的位置是(9 - 3)// 2(其中//是楼层划分)。

            while j<half:
                s[j]=0
                j+=m

现在它又变得容易了。我们发现9;现在我们必须找到15 = 9 + 3 + 3.由于s只包含奇数,所以它只是每个数字列表的一半;为了向前跳过6,我们只需要向j添加3。只要j小于half,我们就会这样做 - 换句话说,只要j小于s的长度。

        i=i+1
        m=2*i+3

同样,简单 - i只是列表的索引,而m是原始数字的值。 (你可以测试一下,看看为什么:[2 * i + 3 for i in range(10)]。)

    return [2]+[x for x in s if x]

并且 voila - 过滤掉筛子中的零,前置[2]并且你有一个素数列表。

这个算法最令人困惑的事情与作者所采用的快捷方式有关,这使得运行速度更快,但却使基本概念失效。 (事实上​​,人们可以采取更多的捷径,但这是另一篇文章。)这是一个更简单的筛子,显示了基本的想法:

>>> numbers = range(40)
>>> numbers[1] = 0    # 1 isn't prime
>>> for i in numbers:
...     if i:
...         for j in range(i + i, len(numbers), i):
...             numbers[j] = 0
... 
>>> [n for n in numbers if n]
[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37]

要拼写出来,第一个数字看起来像这样:

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10...]

则...

[0, 0, 2, 3, 4, 5, 6, 7, 8, 9, 10...]
[0, 0, 2, 3, 0, 5, 0, 7, 0, 9, 0...]
[0, 0, 2, 3, 0, 5, 0, 7, 0, 0, 0...]

等等。

答案 1 :(得分:2)

这是Sieve of Eratosthenes的实现。这需要一些快捷方式 - 例如,不是从'2'开始并且越过所有偶数,它使用范围甚至不生成它们(它从3开始并且仅生成每秒数 - 所以3,5,7等,最多n + 1。

答案 2 :(得分:2)

确实它使用了一些技巧。

  • half约占n的一半。加号和减号就是确保half实际上是最小整数而不是n的一半。
  • m始终是列表创建时的s[i],也等于2*i+3。 (你可以从s的定义中得到它。 m始终是您要消除的倍数的当前素数。
  • j用于消除倍数。它从m平方的索引开始(因为这是需要划掉的最小数字((m*m-3)/2撤消上面的公式以获取值,在本例中为m*m,来自指数)
  • 然后while循环从第一个m开始跨越每个j列表元素。请注意,由于列表只有奇数s[j+m] == s[j]+2m,这是可以的,因为我们正在以这种方式跳过偶数倍。
  • 然后它会迭代i并相应地更新m