Сито Эратосфена-нахождение простых чисел Python
просто чтобы уточнить, это не проблема домашней работы:)
Я хотел найти простые числа для математического приложения, которое я строю и наткнулся решето Эратосфена подход.
Я написал его реализацию в Python. Но это ужасно медленно. Скажем, если я хочу найти все простые числа меньше 2 миллионов. Это занимает > 20 минут. (На этом я остановился). Как я могу ускорить это?
def primes_sieve(limit):
    limitn = limit+1
    primes = range(2, limitn)
    for i in primes:
        factors = range(i, limitn, i)
        for f in factors[1:]:
            if f in primes:
                primes.remove(f)
    return primes
print primes_sieve(2000)
обновление: Я в конечном итоге профилирование этого кода и обнаружил, что довольно много времени было потрачено на удаление элемента из списка. Вполне понятно, учитывая, что он должен пройти весь список (в худшем случае), чтобы найти элемент, а затем удалить его, а затем скорректировать список (может быть, какая-то копия продолжается?). Так или иначе, я выбросил список для словаря. Моя новая реализация -
def primes_sieve1(limit):
    limitn = limit+1
    primes = dict()
    for i in range(2, limitn): primes[i] = True
    for i in primes:
        factors = range(i,limitn, i)
        for f in factors[1:]:
            primes[f] = False
    return [i for i in primes if primes[i]==True]
print primes_sieve1(2000000)
12 ответов
вы не совсем реализуете правильный алгоритм:
в вашем первом примере, primes_sieve не поддерживает список флагов примитивности для удара / снятия (как в алгоритме), но вместо этого постоянно изменяет размер списка целых чисел, что очень дорого: удаление элемента из списка требует смещения всех последующих элементов вниз на один.
во втором примере, primes_sieve1 поддерживает словарь флагов примитивности, что является шагом вправо направление, но оно перебирает словарь в неопределенном порядке и избыточно вычеркивает факторы факторов (а не только факторы простых чисел, как в алгоритме). Вы можете исправить это, сортируя ключи и пропуская не простые числа (что уже делает его на порядок быстрее), но все равно гораздо эффективнее просто использовать список напрямую.
правильный алгоритм (со списком вместо словаря) выглядит примерно так:
def primes_sieve2(limit):
    a = [True] * limit                          # Initialize the primality list
    a[0] = a[1] = False
    for (i, isprime) in enumerate(a):
        if isprime:
            yield i
            for n in xrange(i*i, limit, i):     # Mark factors non-prime
                a[n] = False
(обратите внимание, что это также включает алгоритмическую оптимизацию запуска не-простой маркировки на квадрате простого (i*i) вместо своего двойника.)
def eratosthenes(n):
    multiples = []
    for i in range(2, n+1):
        if i not in multiples:
            print (i)
            for j in range(i*i, n+1, i):
                multiples.append(j)
eratosthenes(100)
удаление из начала массива (списка) требует перемещения всех элементов после него вниз. Это означает, что удаление каждого элемента из списка таким образом, начиная с фронта, является операцией O(n^2).
вы можете сделать это гораздо эффективнее с наборами:
def primes_sieve(limit):
    limitn = limit+1
    not_prime = set()
    primes = []
    for i in range(2, limitn):
        if i in not_prime:
            continue
        for f in range(i*2, limitn, i):
            not_prime.add(f)
        primes.append(i)
    return primes
print primes_sieve(1000000)
... или, альтернативно, избегайте необходимости переставлять список:
def primes_sieve(limit):
    limitn = limit+1
    not_prime = [False] * limitn
    primes = []
    for i in range(2, limitn):
        if not_prime[i]:
            continue
        for f in xrange(i*2, limitn, i):
            not_prime[f] = True
        primes.append(i)
    return primes
Я понимаю, что это на самом деле не отвечает на вопрос о том, как быстро генерировать простые числа, но, возможно, некоторые найдут эту альтернативу интересной: поскольку python обеспечивает ленивую оценку через генераторы, сито Эратосфена может быть реализовано точно так, как указано:
def intsfrom(n):
    while True:
        yield n
        n += 1
def sieve(ilist):
    p = next(ilist)
    yield p
    for q in sieve(n for n in ilist if n%p != 0):
        yield q
try:
    for p in sieve(intsfrom(2)):
        print p,
    print ''
except RuntimeError as e:
    print e
блок try существует, потому что алгоритм работает, пока он не взорвет стек и без попробуйте заблокировать backtrace отображается, нажав фактический выход, который вы хотите видеть за пределами экрана.
объединив вклады многих энтузиастов (включая Гленна Мейнарда и MrHIDEn из комментариев выше), я придумал следующий фрагмент кода в python 2:
def simpleSieve(sieveSize):
    #creating Sieve.
    sieve = [True] * (sieveSize+1)
    # 0 and 1 are not considered prime.
    sieve[0] = False
    sieve[1] = False
    for i in xrange(2,int(math.sqrt(sieveSize))+1):
        if sieve[i] == False:
            continue
        for pointer in xrange(i**2, sieveSize+1, i):
            sieve[pointer] = False
    # Sieve is left with prime numbers == True
    primes = []
    for i in xrange(sieveSize+1):
        if sieve[i] == True:
            primes.append(i)
    return primes
sieveSize = input()
primes = simpleSieve(sieveSize)
время, необходимое для вычисления на моей машине для разных входов мощностью 10:
- 3: 0.3 ms
- 4: 2.4 ms
- 5 : 23 ms
- 6: 0.26 s
- 7: 3.1 s
- 8: 33 s
гораздо быстрее:
import time
def get_primes(n):
  m = n+1
  #numbers = [True for i in range(m)]
  numbers = [True] * m #EDIT: faster
  for i in range(2, int(n**0.5 + 1)):
    if numbers[i]:
      for j in range(i*i, m, i):
        numbers[j] = False
  primes = []
  for i in range(2, m):
    if numbers[i]:
      primes.append(i)
  return primes
start = time.time()
primes = get_primes(10000)
print(time.time() - start)
print(get_primes(100))
простой хак скорости: когда вы определяете переменную "простые числа", установите шаг в 2, чтобы пропустить все четные числа автоматически, и установите начальную точку в 1.
тогда вы можете дополнительно оптимизировать Вместо для i в простых числах, использовать для i в простых числах [: round(len (primes) ** 0.5)]. Это значительно повысит производительность. Кроме того, вы можете исключить числа, заканчивающиеся на 5, чтобы еще больше увеличить скорость.
моя реализация:
import math
n = 100
marked = {}
for i in range(2, int(math.sqrt(n))):
    if not marked.get(i):
        for x in range(i * i, n, i):
            marked[x] = True
for i in range(2, n):
    if not marked.get(i):
        print i
вот версия, которая немного более эффективна для памяти (и: правильное сито, а не пробные подразделения). В принципе, вместо того, чтобы держать массив всех чисел и вычеркивать те, которые не являются простыми, это держит массив счетчиков - по одному для каждого простого, который он обнаружил, - и прыгает их впереди предполагаемого простого. Таким образом, он использует хранилище, пропорциональное числу простых чисел, а не до самого высокого простого.
import itertools
def primes():
    class counter:
        def __init__ (this,  n): this.n, this.current,  this.isVirgin = n, n*n,  True
            # isVirgin means it's never been incremented
        def advancePast (this,  n): # return true if the counter advanced
            if this.current > n:
                if this.isVirgin: raise StopIteration # if this is virgin, then so will be all the subsequent counters.  Don't need to iterate further.
                return False
            this.current += this.n # pre: this.current == n; post: this.current > n.
            this.isVirgin = False # when it's gone, it's gone
            return True
    yield 1
    multiples = []
    for n in itertools.count(2):
        isPrime = True
        for p in (m.advancePast(n) for m in multiples):
            if p: isPrime = False
        if isPrime:
            yield n
            multiples.append (counter (n))
Вы заметите, что primes() - это генератор, таким образом, вы можете сохранить результаты в списке или использовать их напрямую.  Вот первый n простых чисел:
import itertools
for k in itertools.islice (primes(),  n):
    print (k)
и, для полноты, вот таймер для измерения производительности:
import time
def timer ():
    t,  k = time.process_time(),  10
    for p in primes():
        if p>k:
            print (time.process_time()-t,  " to ",  p,  "\n")
            k *= 10
            if k>100000: return
на всякий случай, если вам интересно, я также написал primes() как простой итератор (используя __iter__ и __next__), и он бежал почти с той же скоростью.  Меня это тоже удивило!
Я предпочитаю NumPy из-за скорости.
import numpy as np
# Find all prime numbers using Sieve of Eratosthenes
def get_primes1(n):
    m = int(np.sqrt(n))
    is_prime = np.ones(n, dtype=bool)
    is_prime[:2] = False  # 0 and 1 are not primes
    for i in range(2, m):
        if is_prime[i] == False:
            continue
        is_prime[i*i::i] = False
    return np.nonzero(is_prime)[0]
# Find all prime numbers using brute-force.
def isprime(n):
    ''' Check if integer n is a prime '''
    n = abs(int(n))  # n is a positive integer
    if n < 2:  # 0 and 1 are not primes
        return False
    if n == 2:  # 2 is the only even prime number
        return True
    if not n & 1:  # all other even numbers are not primes
        return False
    # Range starts with 3 and only needs to go up the square root
    # of n for all odd numbers
    for x in range(3, int(n**0.5)+1, 2):
        if n % x == 0:
            return False
    return True
# To apply a function to a numpy array, one have to vectorize the function
def get_primes2(n):
    vectorized_isprime = np.vectorize(isprime)
    a = np.arange(n)
    return a[vectorized_isprime(a)]
Регистрация выход:
n = 100
print(get_primes1(n))
print(get_primes2(n))    
    [ 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]
    [ 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]
сравните скорость сита Эратосфена и грубой силы на ноутбуке Jupyter. Сито Эратосфена в 539 раз быстрее грубой силы для миллиона элементов.
%timeit get_primes1(1000000)
%timeit get_primes2(1000000)
4.79 ms ± 90.3 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
2.58 s ± 31.2 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
Я решил, что можно просто использовать пустой список в качестве условия завершения цикла и придумал это:
limit = 100
ints = list(range(2, limit))   # Will end up empty
while len(ints) > 0:
    prime = ints[0]
    print prime
    ints.remove(prime)
    i = 2
    multiple = prime * i
    while multiple <= limit:
        if multiple in ints:
            ints.remove(multiple)
        i += 1
        multiple = prime * i
import math
def sieve(n):
    primes = [True]*n
    primes[0] = False
    primes[1] = False
    for i in range(2,int(math.sqrt(n))+1):
            j = i*i
            while j < n:
                    primes[j] = False
                    j = j+i
    return [x for x in range(n) if primes[x] == True]
