Как реализовать в Python эффективный бесконечный генератор простых чисел?

Это не домашнее задание, мне просто любопытно.

БЕСКОНЕЧНЫЙ - ключевое слово здесь.

Я хочу использовать его как for p in primes(). Я считаю, что это встроенная функция в Haskell.

Так что ответ не может быть таким наивным, как «Просто сделай сито».

Прежде всего, вы не знаете, сколько последовательных простых чисел будет использовано. Что ж, предположим, вы можете придумывать 100 из них за раз. Вы бы использовали тот же подход Sieve, а также формулу частоты простых чисел?

Я предпочитаю непараллельный подход.

Спасибо, что прочитали (и написали;))!


person Hamish Grubijan    schedule 06.02.2010    source источник
comment
Встроенная функция в Haskell? какой модуль?   -  person st0le    schedule 07.10.2010
comment
Для каких задач вам нужен for p in primes() цикл?   -  person miracle173    schedule 29.11.2014


Ответы (13)


«Если бы я видел дальше…»

Функцию erat2 из кулинарной книги можно дополнительно ускорить (примерно на 20-25%):

erat2a

import itertools as it
def erat2a( ):
    D = {  }
    yield 2
    for q in it.islice(it.count(3), 0, None, 2):
        p = D.pop(q, None)
        if p is None:
            D[q*q] = q
            yield q
        else:
            # old code here:
            # x = p + q
            # while x in D or not (x&1):
            #     x += p
            # changed into:
            x = q + 2*p
            while x in D:
                x += 2*p
            D[x] = p

Проверка not (x&1) подтверждает, что x является нечетным. Однако, поскольку и q, и p нечетные, добавление 2*p позволяет избежать половины шагов вместе с проверкой на странность.

erat3

Если кто-то не против немного лишнего изящества, erat2 можно ускорить на 35-40% с помощью следующих изменений (NB: требуется Python 2.7+ или Python 3+ из-за функции itertools.compress):

import itertools as it
def erat3( ):
    D = { 9: 3, 25: 5 }
    yield 2
    yield 3
    yield 5
    MASK= 1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0,
    MODULOS= frozenset( (1, 7, 11, 13, 17, 19, 23, 29) )

    for q in it.compress(
            it.islice(it.count(7), 0, None, 2),
            it.cycle(MASK)):
        p = D.pop(q, None)
        if p is None:
            D[q*q] = q
            yield q
        else:
            x = q + 2*p
            while x in D or (x%30) not in MODULOS:
                x += 2*p
            D[x] = p

Функция erat3 использует тот факт, что все простые числа (кроме 2, 3, 5) по модулю 30 приводят только к восьми числам: тем, которые включены в MODULOS frozenset. Таким образом, после получения первых трех простых чисел мы начинаем с 7 и работаем только с кандидатами.
Фильтрация кандидатов использует функцию itertools.compress; «магия» находится в MASK последовательности; MASK имеет 15 элементов (15 нечетных чисел в каждых 30 числах, выбранных функцией itertools.islice) с 1 для каждого возможного кандидата, начиная с 7. Цикл повторяется, как указано функцией itertools.cycle.
Введение фильтрация кандидатов требует еще одной модификации: or (x%30) not in MODULOS проверки. Алгоритм erat2 обработал все нечетные числа; Теперь, когда алгоритм erat3 обрабатывает только r30 кандидатов, нам нужно убедиться, что все D.keys() могут быть только такими «ложными» кандидатами.

Контрольные точки

Результаты

На сервере Atom 330 Ubuntu 9.10 версий 2.6.4 и 3.1.1+:

$ testit
up to 8192
==== python2 erat2 ====
100 loops, best of 3: 18.6 msec per loop
==== python2 erat2a ====
100 loops, best of 3: 14.5 msec per loop
==== python2 erat3 ====
Traceback (most recent call last):
…
AttributeError: 'module' object has no attribute 'compress'
==== python3 erat2 ====
100 loops, best of 3: 19.2 msec per loop
==== python3 erat2a ====
100 loops, best of 3: 14.1 msec per loop
==== python3 erat3 ====
100 loops, best of 3: 11.7 msec per loop

На домашнем сервере AMD Geode LX Gentoo Python 2.6.5 и 3.1.2:

$ testit
up to 8192
==== python2 erat2 ====
10 loops, best of 3: 104 msec per loop
==== python2 erat2a ====
10 loops, best of 3: 81 msec per loop
==== python2 erat3 ====
Traceback (most recent call last):
…
AttributeError: 'module' object has no attribute 'compress'
==== python3 erat2 ====
10 loops, best of 3: 116 msec per loop
==== python3 erat2a ====
10 loops, best of 3: 82 msec per loop
==== python3 erat3 ====
10 loops, best of 3: 66 msec per loop

Контрольный код

Модуль primegen.py содержит функции erat2, erat2a и erat3. Вот сценарий тестирования:

#!/bin/sh
max_num=${1:-8192}
echo up to $max_num
for python_version in python2 python3
do
    for function in erat2 erat2a erat3
    do
        echo "==== $python_version $function ===="
        $python_version -O -m timeit -c \
        -s  "import itertools as it, functools as ft, operator as op, primegen; cmp= ft.partial(op.ge, $max_num)" \
            "next(it.dropwhile(cmp, primegen.$function()))"
    done
done
person tzot    schedule 26.09.2010
comment
Это впечатляющий, хотя и запоздалый ответ. Я бы посоветовал проголосовать и другим. - person Hamish Grubijan; 27.09.2010
comment
Спасибо; Обычно я догоняю RSS-канал, и через 3-4 недели у меня появляются вопросы :) - person tzot; 28.09.2010
comment
Кстати, функция erat2a здесь почти полностью совпадает с версией Тима Хохберга из рецептов ActiveState от февраля 2006 г., за исключением того, что он сам считает от 3 с помощью цикла while True. - person Will Ness; 22.05.2012
comment
@WillNess: конечно, erat2a почти то же самое, что erat2 из поваренной книги; Алекс Мартелли упомянул метод поваренной книги («сам и многие другие», который был написан около IIRC 2001-2002 гг.), И я предложил улучшить скорость. Либо ваш комментарий говорит то, что я в основном говорю в первой строке своего ответа, либо вы имели в виду что-то еще, и я упустил вашу точку зрения. - person tzot; 22.05.2012
comment
@WillNess: о, теперь я понимаю вашу точку зрения (которую я упустил :). Да, оба ответа имеют одинаковое ускорение, но это совпадение. И благодаря вам я увидел новый интерфейс (вероятно, лицензированное приложение от stackexchange). Я снова посетил там мою старую учетную запись; первый взнос был 10 лет назад, последний - 5 лет назад. Время летит как стрела (а дрозофилы как банан :). - person tzot; 22.05.2012

Поскольку OP запрашивает эффективную реализацию, здесь существенное улучшение код активного состояния 2002 года Дэвида Эппштейна / Алекса Мартелли (см. здесь, в его ответе): не записывайте информацию о простом числе в словарь, пока его квадрат не появится среди кандидатов. Снижает сложность пространства до O (sqrt (n)) вместо O (n) для созданных n простых чисел ( π (sqrt (n log n)) ~ 2 sqrt (n log n) / log (n log n) ~ 2 sqrt (n / log n)). Следовательно, временная сложность также улучшается, т. Е. выполняется быстрее.

Создает "скользящее решето" как словарь текущих кратных каждому базовому простому числу (т.е. ниже sqrt текущей точки производства) вместе с их значениями step:

from itertools import count
                                         # ideone.com/aVndFM
def postponed_sieve():                   # postponed sieve, by Will Ness      
    yield 2; yield 3; yield 5; yield 7;  # original code David Eppstein, 
    sieve = {}                           #   Alex Martelli, ActiveState Recipe 2002
    ps = postponed_sieve()               # a separate base Primes Supply:
    p = next(ps) and next(ps)            # (3) a Prime to add to dict
    q = p*p                              # (9) its sQuare 
    for c in count(9,2):                 # the Candidate
        if c in sieve:               # c's a multiple of some base prime
            s = sieve.pop(c)         #     i.e. a composite ; or
        elif c < q:  
             yield c                 # a prime
             continue              
        else:   # (c==q):            # or the next base prime's square:
            s=count(q+2*p,2*p)       #    (9+6, by 6 : 15,21,27,33,...)
            p=next(ps)               #    (5)
            q=p*p                    #    (25)
        for m in s:                  # the next multiple 
            if m not in sieve:       # no duplicates
                break
        sieve[m] = s                 # original test entry: ideone.com/WFv4f

(старый исходный код здесь был отредактирован, чтобы включить изменения, указанные в ответе Тим Петерс, см. ниже). см. также this для обсуждения по теме.

Аналогичное колесо 2-3-5-7 - код на основе работает примерно в 2,15 раза быстрее (что очень близко к теоретическому улучшению 3/2 * 5/4 * 7/6 = 2.1875).

person Will Ness    schedule 24.05.2012
comment
Так почему же прекращать жесткое кодирование на Prime = 9, а не на ... скажем, 97? - person Hamish Grubijan; 24.05.2012
comment
9 конечно не простое число; но здесь совершенно произвольно, с чего начать, пока начальное состояние dict D согласуется с начальным кандидатом. Абсолютный минимум дает 3 и начиная с c = 5; Я просто хотел немного задержать рекурсивный вызов postponed_sieve() в строке №5. - person Will Ness; 25.05.2012
comment
К вашему сведению, это не только очень быстро, но и очень эффективно с точки зрения памяти. Например, чтобы найти первый миллион простых чисел, количество записей, которые он помещает в 2 используемых dicts, составляет 545 и 17. Это лучшая реализация, опубликованная на данный момент. - person pts; 02.08.2012
comment
да, это было все намерение, чтобы снизить сложность памяти O (n) до O (sqrt (n)). лучше алгоритмически; вы, вероятно, могли бы сделать больше настроек, чтобы добиться дальнейшего улучшения. - person Will Ness; 03.08.2012
comment
Разве это не приведет к бесконечной рекурсии в этой строке: ps = (p for p in postponed_sieve())? - person ovgolovin; 23.08.2012
comment
@ovgolovin Думаю, я попробовал, и это сработало. Выражения генератора ленивы. Эта строка просто определяет это; тогда требуются только первые два. Но в самом начале я даю четыре простых числа безоговорочно. Так что погони нет. Также есть запись Ideone с аналогичным кодом. (с использованием неотложного сита для подачи простых чисел). - person Will Ness; 24.08.2012
comment
@ovgolovin Кстати, в Haskell выгода намного больше. Dicts Python чрезвычайно эффективны (здесь). Но с вашими объединяющими итераторами вы достигнете SO с 1000 простых чисел, потому что было выполнено 999 слияний . Вместо 24 действительно необходимых. - person Will Ness; 24.08.2012
comment
Спасибо! Я пытаюсь понять ваш код. Пока зря. Надеюсь, в конце концов я пойму, как это работает. Код с рекурсивным postponed_sieve тоже работает. - person ovgolovin; 24.08.2012
comment
с ленивым вычислением (а генераторы ленивы) нет рекурсивного вызова, просто другое определение. Другая вещь создается в памяти, готовая обслуживать следующие запросы, уступая им, значение за значением. Итак, есть два; один кормит другой; верхний подает печать верхнего уровня или что-то еще и извлекает простые числа из нижнего, когда ему (верхнему) нужно новое простое число - и это когда его квадрат поступил на вход, c = c+2. - person Will Ness; 24.08.2012
comment
Вы понимаете, как код не отложенного рецепта активного состояния Дэвид Эппштейн работает? Начни с этого. Это прямо здесь, на этой самой странице. - person Will Ness; 24.08.2012
comment
Спасибо! Думаю, со временем я понял, как это работает! Вот код с выводами отладки для тех, кому будет сложно его понять: ideone.com/n5PGu. И я это понял только тогда, когда нарисовал сгенерированные простые числа на бумаге цветными ручками. : o) - person ovgolovin; 01.09.2012
comment
Если вы не возражаете, у меня есть еще один вопрос. Субитератор выполняет ту же работу, что и первый итератор. Почему бы вам просто не сохранить вывод первого итератора в список, а итератор по этим значениям в подитераторе. Я имею в виду создать список primes = [], а затем помимо добавления значений в primes и заставить подитератор перебирать сохраненные значения ps = (p for p in iter(primes)). См. primes5: ideone.com/QV4EQ (время неверно из-за большого print заявления) - person ovgolovin; 01.09.2012
comment
Я провел приличную проверку времени. Результаты: ideone.com/xeke4 Версия со значениями запоминания немного медленнее, чем версия с рекурсивными вызовами генератора. Я думаю, это потому, что добавление в список довольно утомительно. - person ovgolovin; 01.09.2012
comment
Дополнительные тайминги: ideone.com/z9dHG (невозможно увеличить n в ideone, потому что ограничения на время расчета; просто скопируйте код в локальный интерпретатор Python 3 и выполните с большими константами в main2). - person ovgolovin; 01.09.2012
comment
Сложность по времени O(n). Но константы намного ниже, чем в неотложном сите. (20 000 000 простых чисел генерируются на моем компьютере за 90 секунд с использованием PyPy. Отлично!) - person ovgolovin; 01.09.2012
comment
@ovgolovin Мне просто нужна была отдельная поставка простых чисел - поэтому я использовал то, что у меня уже было - сам генератор (это еще одна копия, но нет необходимости писать какой-либо новый код). Просто хотел чего-нибудь - чего угодно - для работы. :) Совет: сначала убедитесь, что код работает, соответствует установленным вами законам - потом забудьте о его внутреннем устройстве, с этого момента относитесь к нему как к черному ящику. Например. добавление в словарь в этом коде. Нет необходимости возвращаться к тому, как это делается. Мы просто знаем, что он содержит числа, кратные 1 для каждого простого числа, меньшего, чем текущий sqrt. :) - person Will Ness; 01.09.2012
comment
@ovgolovin повторно использует списки - но откуда вы возьмете простые числа, чтобы добавить их в конец списка? Это поколение уже далеко: оно где-то около 121, когда нам нужно добавить 11. Все дело в том, что мы хотим иметь возможность забыть произведенное простое число, как только оно будет произведено. --- относительная временная сложность: теоретический нижний предел составляет `~ n log (n) log (log (n)) _ 1_n` произведенных простых чисел. Проверьте это: en.wikipedia.org/wiki/. - person Will Ness; 01.09.2012
comment
@WillNess Да. Версия со списком primes содержит все сгенерированные простые числа. Я подумал, что это избавит нас от лишней работы подгенератора простых чисел. Но сохранение этих значений происходит даже медленнее, чем запуск субгенератора, не говоря уже о том, что все сохраненные значения потребляют память. - person ovgolovin; 01.09.2012
comment
@ovgolovin выбросить все простые числа - вот и весь смысл всей этой операции. :) :) При увеличении размера дикта производительность ухудшается. Внутренние подгенераторы выполняют минимально возможную работу по поддержанию там башни подгенераторов. Кстати, в моем коде ideone нет башни - я использую неотложное сито для внутреннего подгенератора. Но позволить этому отложенному парню позаботиться о себе автоматически было тоже весело. Просто сначала произведите эти 2-3-5-7, и все в порядке. :) - person Will Ness; 01.09.2012
comment
Мне так нравится ваше решение, я бы хотел (лениво! :) + ∞ - person tzot; 02.05.2013
comment
@WillNess: В качестве упражнения я попытался реализовать ваше решение на Swift и представил его в Code Review: codereview.stackexchange.com/questions/136332/. - person Martin R; 29.07.2016

Для потомков - это переписанный вариант прекрасного алгоритма Уилла Несса для Python 3. Необходимы некоторые изменения (итераторы больше не имеют .next() методов , но есть новая встроенная функция next()). Другие изменения сделаны для развлечения (использование нового yield from <iterable> заменяет четыре оператора yield в оригинале. Больше для удобства чтения (я не фанат чрезмерного использования ;-) однобуквенных имен переменных).

Это значительно быстрее оригинала, но не по алгоритмам. Ускорение в основном связано с удалением исходной функции add(), которая вместо этого выполняется встроенной.

def psieve():
    import itertools
    yield from (2, 3, 5, 7)
    D = {}
    ps = psieve()
    next(ps)
    p = next(ps)
    assert p == 3
    psq = p*p
    for i in itertools.count(9, 2):
        if i in D:      # composite
            step = D.pop(i)
        elif i < psq:   # prime
            yield i
            continue
        else:           # composite, = p*p
            assert i == psq
            step = 2*p
            p = next(ps)
            psq = p*p
        i += step
        while i in D:
            i += step
        D[i] = step
person Tim Peters    schedule 15.10.2013
comment
я сказал спасибо? Я должен был это сделать, когда проголосовал за (еще в апреле, как мне говорит SO). :) Это очень мило, особенно. утверждения. Конечно, основная красота принадлежит первоначальному автору (авторам). - person Will Ness; 20.10.2014
comment
Наоборот, спасибо, Уилл! Я один из соавторов рецепта ActiveState - мы все весело провели время, работая над исходным алгоритмом на comp.lang.python. Это дало хороший алгоритм. Но ни у кого из нас не было той идеи, которую вы добавили, чтобы отложить добавление кратных к словарю до того, как они действительно понадобятся. Это очень красиво и приносит реальную практическую пользу - спасибо! - person Tim Peters; 21.10.2014
comment
Насколько быстро это работает по сравнению с исходным ситом + 2,3,5,7? - person Oscar Smith; 02.11.2016
comment
Я отредактировал, чтобы добавить ссылку на упомянутый ответ, чтобы его было легче найти. - person Will Ness; 20.12.2020

Это изначально не мой код, но его стоит опубликовать. Оригинал можно найти здесь: http://code.activestate.com/recipes/117119/

def gen_primes():
  D = {}
  q = 2  # first integer to test for primality.

  while True:
    if q not in D:
      # not marked composite, must be prime  
      yield q 

      #first multiple of q not already marked
      D[q * q] = [q] 
    else:
      for p in D[q]:
        D.setdefault(p + q, []).append(p)
      # no longer need D[q], free memory
      del D[q]

    q += 1

Это генератор, поэтому используйте его, как и любой другой.

primes = gen_primes()
for p in primes:
  print p

Чтобы сгенерировать и поместить в набор из 1 миллиона простых чисел на моем рабочем столе, требуется 1,62 с.

person Dominic Bou-Samra    schedule 06.02.2010
comment
Как это масштабируется? Пожалуйста, вставьте сюда первый триллион простых чисел. - person Beska; 06.02.2010
comment
@Beska: Меня больше интересуют простые числа от двух до трех триллионов. Кто хотел бы проверить их для меня? - person D.Shawley; 06.02.2010
comment
10 миллионов простых чисел занимает 19,26 секунды. 100 миллионов простых чисел заняли 293,61 секунды. Может кто-нибудь еще проверит - возможно, я делаю неправильно: S - person Dominic Bou-Samra; 06.02.2010
comment
Есть ли у кого-нибудь здесь чувство, что здесь происходит что-то подозрительное? Опубликуйте простые числа, человек ... это круто ... Я не хочу никаких проблем ... просто разместите простые числа ... - person Beska; 06.02.2010
comment
@Hamish: почему бы тебе просто не запустить это самому, взять простые числа и посмотреть на них на досуге? (Вместо того, чтобы забивать этот вопрос / ответ огромным количеством бессмысленных данных.) - person Beska; 06.02.2010
comment
@Beska, ты не против задать мой вопрос прямо перед этим? Спасибо! - person Hamish Grubijan; 06.02.2010
comment
у вас чертовски быстрый рабочий стол, очевидно, более чем в 20 раз быстрее, чем у Ideone (обычно около 7 раз, я подумал ... может для Python все по-другому). - person Will Ness; 03.12.2014

Сделайте сегментированное сито, где размер сегмента определяется доступной памятью или максимальным размером битового набора.

Для каждого сегмента представляют числа в некотором интервале [n; n + segment_size) как набор бит и решето со всеми простыми числами ниже квадратного корня из верхней границы.

Использование набора битов требует меньше памяти, чем хэш-таблица или древовидная структура данных, потому что вы работаете с плотными наборами чисел.

person starblue    schedule 06.02.2010
comment
Моя реализация делает что-то вроде сегментированного сита, но использует две кучи вместо битовых наборов. stackoverflow.com/a/11759904/97248 - person pts; 01.08.2012

Другой способ сделать это:

import itertools
def primeseq():
    prime = [2]
    num = 0
    yield 2
    for i in itertools.count(3, 2):
        is_prime = True
        for num in prime:
            if i % num == 0:
                is_prime = False
                break
            elif num ** 2 > i: 
                break
        if is_prime:
            prime.append(i)
            yield i
person quantum    schedule 05.02.2012
comment
это оптимальный алгоритм пробного деления. - person Will Ness; 02.08.2012

И еще один ответ, более эффективный с точки зрения памяти, чем мой erat3 ответ здесь:

import heapq

def heapprimegen():
    hp= []
    yield 2
    yield 3
    cn= 3
    nn, inc= 3, 6
    while 1:
        while cn < nn:
            yield cn
            heapq.heappush(hp, (3*cn, 2*cn))
            cn+= 2
        cn= nn+2
        nn, inc= heapq.heappushpop(hp, (nn+inc, inc))

Он поддерживает кучу (список) простых кратных, а не словарь. Очевидно, он немного теряет скорость.

person tzot    schedule 05.12.2011
comment
yield 3 здесь отсутствует. - person pts; 01.08.2012

Вот довольно быстрый бесконечный генератор, написанный на Python2, но легко настраиваемый на Python3. Чтобы использовать его для добавления простых чисел до 10 ** 9, используйте следующее:

from itertools import takewhile
from functools import partial
from operator import gt
print (sum(takewhile(partial(gt, 10**9), prime_gen_inf())))

Это сегментированное решето, более быстрое, но явно менее элегантное, чем алгоритм Уилла Несса.

from operator import mul
from functools import reduce
def prod(x): return reduce(mul, x, 1)


def build_sieve(wheel):
    w = prod(wheel)
    w_phi = prod([p-1 for p in wheel])
    rems = [a for a in range(w) if all(a % p for p in wheel)]
    assert len(rems) == w_phi
    inv = {a:pow(a, w_phi - 1, w) for a in rems}
    try:
        known_p = wheel + rems[1 : rems.index(rems[1]*rems[1])]
    except ValueError:
        known_p = wheel + rems[1:]
    return wheel, w, w_phi, rems, inv, known_p

#Adjust the chunk variable based on your computer's architecture.
#
#Adjust the line with #! if you don't need "true" infinite.  If you don't need
#primes larger than 1<<32, use array('H', []), if 1<<64 use 'L', if 1<<128 (in
#Python3) use 'Q', otherwise use empty list [].
#To save memory, comment out the lines with #*, and uncomment the commented-out
#lines 
import itertools
from itertools import islice, count, compress, izip
chain_f = itertools.chain.from_iterable
from array import array
def prime_gen_inf(chunk=250000, sieve_info = build_sieve([2,3,5,7])):
    """    Indefinitely yields primes    """
    wheel, w, w_phi, rems, inv, known_p = sieve_info
    for p in known_p: yield p
    new_n = 0;
    while True:
        size = min(chunk, (p * p - new_n) / w)
        sieve = bytearray([1]) * size * w_phi
        n, new_n = new_n, new_n + size * w
        if not n:
            zero = bytearray([0])
            seen = len(known_p) - len(wheel) + 1
            sieve[:seen:1] = zero * seen
            p_gen = islice(prime_gen_inf(), len(wheel), None)
            new_p = next(p_gen)
            ps = []                                         #! array('H', [])
            p_invs = bytearray([])                                         #*
        while new_p * new_p < new_n:
            ps.append(new_p)
            p_invs.append(inv[new_p % w])                                  #*
            new_p = next(p_gen)
        for p, p_inv, modp in izip(ps, p_invs, [-n % p for p in ps]):      #*
            s = [(modp + p * (p_inv * (r - modp) % w)) / w for r in rems]  #*
        #for p in ps:
        #    s = [(-n%p + p * (inv[p%w] * (r - -n%p) % w)) / w for r in rems]
            for i, start in enumerate(s):
                slice_size = ((size - start - 1) / p + 1)
                sieve[i + start * w_phi :: p * w_phi] = zero * slice_size
        for p in compress(chain_f(izip(*[count(n+r, w) for r in rems])), sieve):
            yield p
person Jason    schedule 05.11.2015

Вот сложная реализация на основе кучи, которая не намного быстрее, чем другие реализации на основе кучи (см. Сравнение скорости в другом моем ответе), но она использует гораздо меньше памяти.

Эта реализация использует две кучи (tu и wv), которые содержат одинаковые числовые элементы. Каждый элемент представляет собой пару int. Чтобы найти все простые числа до q**2 (где q - простое число), каждая куча будет содержать не более 2*pi(q-1) элементов, где pi(x) - количество положительных простых чисел, не превышающих x. Таким образом, общее количество целых чисел не превышает 4*pi(floor(sqrt(n))). (Мы могли бы увеличить объем памяти вдвое, поместив в кучу вдвое меньше данных, но это замедлило бы алгоритм.)

Другие подходы на основе dict и кучи (например, erat2b, heap_prime_gen_squares и heapprimegen) выше хранят около целых чисел `2 * pi (n) ', потому что они расширяют свою кучу или dict каждый раз, когда находят простое число. Для сравнения: чтобы найти простые числа 1_000_000, эта реализация хранит менее 4141 целых чисел, другие реализации хранят более 1_000_000 целых чисел.

import heapq

def heap_prime_gen_smallmem():
    yield 2
    yield 3
    f = 5
    fmar3 = 2
    q = 7
    q6 = 7 * 6
    qmar3 = 4
    tu = [(25, 30), (35, 30)]
    vw = [(25, 30), (35, 30)]
    while True:
        qmar3 += 2   
        if qmar3 == 6:  
            qb = q + 4
            q6b = q6 + 24
            qmar3 = 2
        else:
            qb = q + 2
            q6b = q6 + 12
        if q < tu[0][0]:
            d = q * q
            while f < d:
                a, b = vw[0]
                if f < a: 
                    yield f   
                else:
                    a, b = vw[0]
                    heapq.heapreplace(vw, (a + b, b))
                    a, b = vw[0]
                    while f >= a:
                        heapq.heapreplace(vw, (a + b, b))
                        a, b = vw[0]   
                fmar3 += 2
                if fmar3 == 6:
                    f += 4
                    fmar3 = 2
                else:
                    f += 2
            c = q * qb   
            heapq.heappush(tu, (d, q6))
            heapq.heappush(tu, (c, q6))
            heapq.heappush(vw, (d, q6))
            heapq.heappush(vw, (c, q6))
        else:
            a, b = tu[0]
            heapq.heapreplace(tu, (a + b, b))
            a, b = tu[0]  
            while q >= a:
                heapq.heapreplace(tu, (a + b, b))
                a, b = tu[0]
        q = qb
        q6 = q6b
person pts    schedule 01.08.2012

Вот простой, но не очень медленный вариант, использующий кучу вместо dict:

import heapq

def heap_prime_gen_squares(): 
    yield 2  
    yield 3  
    h = [(9, 6)]
    n = 5
    while True:
        a, b = h[0]
        while n < a:
            yield n
            heapq.heappush(h, (n * n, n << 1))
            n += 2
        heapq.heapreplace(h, (a + b, b))  # Replace h[0], which is still (a, b).

Мои измерения скорости пользовательского времени для первого миллиона простых чисел (меньшие числа лучше):

  • postponed_sieve (на основе dict): 8.553 с
  • erat2b (на основе диктовки): 9,513 с
  • erat2a (на основе диктовки): 10,313 с
  • heap_prime_gen_smallmem (на основе кучи): 23.935 с
  • heap_prime_gen_squares (на основе кучи): 27,302 с
  • heapprimegen (на основе диктовки): 145.029 с

Так что подходы, основанные на диктовке, кажутся самыми быстрыми.

person pts    schedule 01.08.2012

Вот генератор, который немного ближе к тому, как это делается в Haskell: фильтрация против составов известных простых чисел, а затем добавление оставшихся простых чисел в список.

def gen_primes():
    primes = []
    i = 2
    while True:
        prime = True
        for p in primes:
            if not (i % p):
                prime = False
                break
        if prime:
            yield i
            primes.append(i)
        i += 1
person avpx    schedule 06.02.2010
comment
Это не обязательно эффективно, но очень похоже на однострочную реализацию решета Эратосфена в Haskell. Это мой код, и я только что его написал, поэтому он может работать не так, как задумано, но его быстрая проверка действительно генерирует правильную последовательность простых чисел. - person avpx; 06.02.2010
comment
Это действительно зависло для меня. Каков код для генерации первых 100? - person Hamish Grubijan; 06.02.2010
comment
Это странно. У меня отлично работает. Попробуйте это: primes = gen_primes(), а затем for i in xrange(100): print primes.next() - person avpx; 06.02.2010
comment
Это похоже на ответ, опубликованный позже Quant, за исключением того, что этот код проверяет каждого кандидата i на каждое простое число, которое мы видели до сих пор. Он должен выйти из внутреннего цикла, когда p*p > i. - person PM 2Ring; 01.12.2020

Несколько раз назад я написал статью о генераторе бесконечных простых чисел:

http://stacktrace.it/2008/01/progetto-eulero-problema-3/

Это на итальянском, но у вас может быть неприятный перевод с помощью Google: http://tinyurl.com/yzpyeom

person piro    schedule 06.02.2010
comment
Быстрый комментарий к вашей статье: разложение на простые множители может быть выполнено без необходимости заранее генерировать какие-либо простые числа: используя другой метод, который знает каждый школьник, начиная с 2, разделите все 2 из N, затем все 3 , Комбинации 5, 7, 9 и т. Д. (Просто шансы, поскольку все двойки уже были разделены). Я тоже решил эту проблему на сайте Эйлера и использовал простое производящее сито, но это совершенно не нужно. С помощью этой техники любое найденное вами число, делящее N, будет простым (простым множителем). - person Bogatyr; 21.10.2011

Я знаю, что сообщение старое, но я сам натолкнулся на этот вопрос ... Следующий код основан на очень простой идее: растущее сито Эратосфена. Это решение действительно медленнее, чем лучшие здесь, но его легко понять и оно разработано так, чтобы его можно было читать ...

Я использовал целые числа для хранения результатов сита. В двоичном формате целое число представляет собой список 0s и 1s, 0 в позиции i, если i не является простым числом, 1, если оно может быть простым. Необходимая бесконечность является результатом того факта, что целые числа Python 3 неограничены.

def primes():
    container, size = 1 << 2, 3 # we start with 0b100 (from right to left: 0 and 1 are not primes, 2 is
    last_prime = 1
    while True:
        prime = next((j for j in range(last_prime+1, size) if container & 1 << j), None) # find the next prime
        while not prime:
            container, size = expand(container, size, 2**16) # add 65536 cells and sieve the container
            prime = next((j for j in range(last_prime+1, size) if container & 1 << j), None)
        yield prime
    last_prime = prime

Как расширить емкость? Просто добавьте кучу 1 слева от контейнера (в двоичном формате) и просейте их. Оно идентично стандартному сито с небольшой разницей. В стандартном сите, если мы находим простое число i, мы начинаем пересекать ячейки с i*i с шагом i.

Здесь это могло быть сделано для первой части контейнера. Нам просто нужно начать с начала новой части контейнера, если она дальше, чем i*i.

def expand(container, size, n):
    new_size = size + n
    container += (1 << (new_size + 1) - 1) - (1 << size) # add n 1's
    for i in range(2, new_size):
        if container & (1 << i): # i is a prime
            t = sum(1 << j for j in range(max(i, size // i)*i, new_size, i)) # set 1 for all mutiple
            container &= ~t # cross the cells

    return container, new_size

Тест на миллион простых чисел:

import itertools
assert 78498 == len(list(itertools.takewhile(lambda p: p<1000000, primes())))
person jferard    schedule 17.05.2018