Сито Эратосфена-поиск простых чисел питона
просто чтобы уточнить, это не проблема с домашним заданием:)
Я хотел найти простые числа для математического приложения я строю и наткнулся решето Эратосфена подход.
Я написал его реализацию в 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поддерживает a словарь флагов примитивности, что является шагом вправо направление, но он перебирает словарь в неопределенном порядке и избыточно вычеркивает факторы факторов (а не только факторы простых чисел, как в алгоритме). Вы можете исправить это, отсортировав ключи и пропустив не простые числа (что уже делает его на порядок быстрее), но все же гораздо эффективнее просто использовать список напрямую.правильный алгоритм (со списком вместо словаря) выглядит примерно так:
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.
тогда вы можете дополнительно оптимизировать вместо for i в простых числах, использовать for 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]
Comments