Модуль быстрой простой факторизации


Я ищу реализация или четкий алгоритм для получения простой факторизации N в любом python, псевдокод или что-нибудь еще хорошо читается. Есть несколько требований / фактов:

  • N между 1 и ~20 цифр
  • нет предварительно рассчитанной таблицы поиска, хотя memoization в порядке.
  • не нужно быть математически доказанным (например, может полагаться на гипотезу Гольдбаха при необходимости)
  • не нужно быть точным, допускается быть вероятностным / детерминированным, если это необходимо

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

Я пробовал другие алгоритмы из Википедии и такие, но либо я не мог понять их (ECM), либо я не мог создать рабочую реализацию из алгоритма (Поллард-Брент).

Я действительно заинтересован в алгоритме Полларда-Брента, поэтому любая дополнительная информация/реализация на нем была бы очень приятной.

спасибо!

редактировать

после небольшого беспорядка я создал довольно быстрый модуль prime / factorization. Он сочетает в себе оптимизированный алгоритм пробного разделения, алгоритм Полларда-Брента, тест на примитивность Миллера-Рабина и самый быстрый primesieve, который я нашел в интернете. НОД-это регулярная реализация GCD Евклида (двоичный GCD Евклида много медленнее, чем обычные).

награда

о, радость, щедрость наживное! Но как я могу его выиграть?

  • найти оптимизацию или ошибку в моем модуле.
  • обеспечить альтернативные/лучшие алгоритмы/реализации.

ответ, который является наиболее полной/конструктивной получает награду.

и, наконец, модуль сам по себе:

import random

def primesbelow(N):
    # http://stackoverflow.com/questions/2068372/fastest-way-to-list-all-primes-below-n-in-python/3035188#3035188
    #""" Input N>=6, Returns a list of primes, 2 <= p < N """
    correction = N % 6 > 1
    N = {0:N, 1:N-1, 2:N+4, 3:N+3, 4:N+2, 5:N+1}[N%6]
    sieve = [True] * (N // 3)
    sieve[0] = False
    for i in range(int(N ** .5) // 3 + 1):
        if sieve[i]:
            k = (3 * i + 1) | 1
            sieve[k*k // 3::2*k] = [False] * ((N//6 - (k*k)//6 - 1)//k + 1)
            sieve[(k*k + 4*k - 2*k*(i%2)) // 3::2*k] = [False] * ((N // 6 - (k*k + 4*k - 2*k*(i%2))//6 - 1) // k + 1)
    return [2, 3] + [(3 * i + 1) | 1 for i in range(1, N//3 - correction) if sieve[i]]

smallprimeset = set(primesbelow(100000))
_smallprimeset = 100000
def isprime(n, precision=7):
    # http://en.wikipedia.org/wiki/Miller-Rabin_primality_test#Algorithm_and_running_time
    if n < 1:
        raise ValueError("Out of bounds, first argument must be > 0")
    elif n <= 3:
        return n >= 2
    elif n % 2 == 0:
        return False
    elif n < _smallprimeset:
        return n in smallprimeset


    d = n - 1
    s = 0
    while d % 2 == 0:
        d //= 2
        s += 1

    for repeat in range(precision):
        a = random.randrange(2, n - 2)
        x = pow(a, d, n)

        if x == 1 or x == n - 1: continue

        for r in range(s - 1):
            x = pow(x, 2, n)
            if x == 1: return False
            if x == n - 1: break
        else: return False

    return True

# https://comeoncodeon.wordpress.com/2010/09/18/pollard-rho-brent-integer-factorization/
def pollard_brent(n):
    if n % 2 == 0: return 2
    if n % 3 == 0: return 3

    y, c, m = random.randint(1, n-1), random.randint(1, n-1), random.randint(1, n-1)
    g, r, q = 1, 1, 1
    while g == 1:
        x = y
        for i in range(r):
            y = (pow(y, 2, n) + c) % n

        k = 0
        while k < r and g==1:
            ys = y
            for i in range(min(m, r-k)):
                y = (pow(y, 2, n) + c) % n
                q = q * abs(x-y) % n
            g = gcd(q, n)
            k += m
        r *= 2
    if g == n:
        while True:
            ys = (pow(ys, 2, n) + c) % n
            g = gcd(abs(x - ys), n)
            if g > 1:
                break

    return g

smallprimes = primesbelow(1000) # might seem low, but 1000*1000 = 1000000, so this will fully factor every composite < 1000000
def primefactors(n, sort=False):
    factors = []

    for checker in smallprimes:
        while n % checker == 0:
            factors.append(checker)
            n //= checker
        if checker > n: break

    if n < 2: return factors

    while n > 1:
        if isprime(n):
            factors.append(n)
            break
        factor = pollard_brent(n) # trial division did not fully factor, switch to pollard-brent
        factors.extend(primefactors(factor)) # recurse to factor the not necessarily prime factor returned by pollard-brent
        n //= factor

    if sort: factors.sort()

    return factors

def factorization(n):
    factors = {}
    for p1 in primefactors(n):
        try:
            factors[p1] += 1
        except KeyError:
            factors[p1] = 1
    return factors

totients = {}
def totient(n):
    if n == 0: return 1

    try: return totients[n]
    except KeyError: pass

    tot = 1
    for p, exp in factorization(n).items():
        tot *= (p - 1)  *  p ** (exp - 1)

    totients[n] = tot
    return tot

def gcd(a, b):
    if a == b: return a
    while b > 0: a, b = b, a % b
    return a

def lcm(a, b):
    return abs((a // gcd(a, b)) * b)
8 59

8 ответов:

если вы не хотите изобретать велосипед, используйте библиотека sympy

pip install sympy

использование функции sympy.ntheory.factorint

>>> from sympy.ntheory import factorint
>>> factorint(10**20+1)
{73: 1, 5964848081: 1, 1676321: 1, 137: 1}

вы можете разложить некоторые очень большие числа:

>>> factorint(10**100+1)
{401: 1, 5964848081: 1, 1676321: 1, 1601: 1, 1201: 1, 137: 1, 73: 1, 129694419029057750551385771184564274499075700947656757821537291527196801: 1}

нет необходимости расчета smallprimes С помощью primesbelow используйте smallprimeset для этого.

smallprimes = (2,) + tuple(n for n in xrange(3,1000,2) if n in smallprimeset)

разделить primefactors на две функции для обработки smallprimes и pollard_brent, это может сэкономить пару итераций, так как все степени smallprimes будут разделены от n.

def primefactors(n, sort=False):
    factors = []

    limit = int(n ** .5) + 1
    for checker in smallprimes:
        print smallprimes[-1]
        if checker > limit: break
        while n % checker == 0:
            factors.append(checker)
            n //= checker


    if n < 2: return factors
    else : 
        factors.extend(bigfactors(n,sort))
        return factors

def bigfactors(n, sort = False):
    factors = []
    while n > 1:
        if isprime(n):
            factors.append(n)
            break
        factor = pollard_brent(n) 
        factors.extend(bigfactors(factor,sort)) # recurse to factor the not necessarily prime factor returned by pollard-brent
        n //= factor

    if sort: factors.sort()    
    return factors

рассматривая проверенные результаты Pomerance, Selfridge и Wagstaff и Jaeschke, вы можете уменьшить повторения в isprime который использует Миллер-Рабин тест на первичность. От Wiki.

  • если n
  • если n
  • если n
  • если n
  • если n
  • если n

изменить 1: исправлен обратный звонок if-else чтобы добавить bigfactors к факторам в primefactors.

даже на текущем, есть несколько пятен, чтобы быть замеченным.

  1. не checker*checker каждый цикл, используйте s=ceil(sqrt(num)) и checher < s
  2. Чечер должен плюс 2 каждый раз, игнорировать все четные числа, кроме 2
  3. использовать divmod вместо % и //

вы, вероятно, должны сделать некоторые простые обнаружения, которые вы могли бы посмотреть здесь, быстрый алгоритм поиска простых чисел?

вы должны прочитать весь этот блог, хотя, есть несколько алгоритмов, которые он перечисляет для тестирования примитивности.

есть библиотека python с коллекцией тестов на примитивность (включая неправильные для того, что не нужно делать). Это называется pyprimes. Решил, что это стоит упомянуть для целей потомства. Я не думаю, что это включает в себя алгоритмы, которые вы упомянули.

я просто столкнулся с ошибкой в этом коде при факторинге числа 2**1427 * 31.

  File "buckets.py", line 48, in prettyprime
    factors = primefactors.primefactors(n, sort=True)
  File "/private/tmp/primefactors.py", line 83, in primefactors
    limit = int(n ** .5) + 1
OverflowError: long int too large to convert to float

этот фрагмент кода:

limit = int(n ** .5) + 1
for checker in smallprimes:
    if checker > limit: break
    while n % checker == 0:
        factors.append(checker)
        n //= checker
        limit = int(n ** .5) + 1
        if checker > limit: break

следует переписать в

for checker in smallprimes:
    while n % checker == 0:
        factors.append(checker)
        n //= checker
    if checker > n: break

который, вероятно, будет работать быстрее на реалистичных входах в любом случае. Квадратный корень медленный-в основном эквивалент многих умножений -,smallprimes всего несколько десятков членов, и таким образом мы убираем расчета n ** .5 от плотной внутренней петли, которая определенно полезна когда факторинг чисел, как 2**1427. Там просто нет причин, чтобы вычислить sqrt(2**1427),sqrt(2**1426),sqrt(2**1425) и т. д. так далее., когда все, о чем мы заботимся, это "превышает ли [квадрат] checker n".

переписанный код не вызывает исключений при представлении больших чисел,и это примерно в два раза быстрее по timeit (на входах выборки 2 и 2**718 * 31).

также обратите внимание, что isprime(2) возвращает неправильный результат, но это нормально, пока как мы на это не рассчитываем. IMHO вы должны переписать вступление этой функции как

if n <= 3:
    return n >= 2
...

вы можете факторизовать до предела, а затем использовать brent для получения более высоких коэффициентов

from fractions import gcd
from random import randint

def brent(N):
   if N%2==0: return 2
   y,c,m = randint(1, N-1),randint(1, N-1),randint(1, N-1)
   g,r,q = 1,1,1
   while g==1:             
       x = y
       for i in range(r):
          y = ((y*y)%N+c)%N
       k = 0
       while (k<r and g==1):
          ys = y
          for i in range(min(m,r-k)):
             y = ((y*y)%N+c)%N
             q = q*(abs(x-y))%N
          g = gcd(q,N)
          k = k + m
       r = r*2
   if g==N:
       while True:
          ys = ((ys*ys)%N+c)%N
          g = gcd(abs(x-ys),N)
          if g>1:  break
   return g

def factorize(n1):
    if n1==0: return []
    if n1==1: return [1]
    n=n1
    b=[]
    p=0
    mx=1000000
    while n % 2 ==0 : b.append(2);n//=2
    while n % 3 ==0 : b.append(3);n//=3
    i=5
    inc=2
    while i <=mx:
       while n % i ==0 : b.append(i); n//=i
       i+=inc
       inc=6-inc
    while n>mx:
      p1=n
      while p1!=p:
          p=p1
          p1=brent(p)
      b.append(p1);n//=p1 
    if n!=1:b.append(n)   
    return sorted(b)

from functools import reduce
#n= 2**1427 * 31 #
n= 67898771  * 492574361 * 10000223 *305175781* 722222227*880949 *908909
li=factorize(n)
print (li)
print (n - reduce(lambda x,y :x*y ,li))