Модуль швидкого розкладання простих факторів


79

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

  • N - від 1 до ~ 20 цифр
  • Немає заздалегідь розрахованої таблиці пошуку, хоча мемоізація чудова.
  • Потрібно не доводити математично (наприклад, якщо потрібно, можна покластися на гіпотезу Гольдбаха)
  • Не потрібно бути точним, дозволяється бути імовірнісним / детермінованим, якщо це необхідно

Мені потрібен швидкий алгоритм розкладання простих факторів, не тільки для себе, але і для використання в багатьох інших алгоритмах, таких як обчислення Ейлера phi (n) .

Я пробував інші алгоритми з Вікіпедії та інші, але або я не міг їх зрозуміти (ECM), або не міг створити робочу реалізацію з алгоритму (Pollard-Brent).

Мене дуже цікавить алгоритм Полларда-Брента, тому будь-яка додаткова інформація / реалізації щодо нього була б дуже приємною.

Дякую!

РЕДАГУВАТИ

Трохи попсувавшись, я створив досить швидкий модуль простого / розкладання на факторизацію. Він поєднує в собі оптимізований алгоритм пробного розподілу, алгоритм Полларда-Брента, тест на первинність Міллера-Рабіна та найшвидший праймсіве, який я знайшов в інтернеті. gcd - це звичайна реалізація 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)

1
@wheaties - ось для чого while checker*checker <= numце.
Бурштин

Ви можете знайти цю нитку корисним: stackoverflow.com/questions/1024640/calculating-phik-for-1kn / ...
RBarryYoung

Чому подібні речі не доступні у стандартній бібліотеці? Коли я шукаю, все, що я знаходжу, це мільйон пропозицій щодо рішення проекту Ейлера, а інші люди вказують на їх вади. Чи не для цього призначені бібліотеки та звіти про помилки?
ендоліт

@endolith Поза такими речами, як Prject Euler, для цього мало користі. Звичайно, недостатньо, щоб помістити його в стандартні бібліотеки.
orlp

@nightcracker: Немає практичного використання факторизації чисел ??
ендоліт

Відповіді:


70

Якщо ви не хочете винаходити колесо заново, скористайтесь бібліотекою 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}

3
Дякуємо, що поділилися цим, @Colonel Panic. Це саме те , що я шукав: цілочисельний факторизаційний модуль у доглянутій бібліотеці, а не фрагменти коду.
програміст

15

Не потрібно обчислювати smallprimesвикористання primesbelow, використовуйте smallprimesetдля цього.

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

Розділіть свої primefactorsфункції на дві функції для обробки smallprimesта інші для pollard_brent, це може заощадити пару ітерацій, оскільки всі повноваження малих праймів будуть розділені на 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

Розглядаючи перевірені результати Померанса, Селріджа та Вагстафа та Яшке, ви можете зменшити кількість повторень, в isprimeяких використовується тест первинності Міллера-Рабіна. З Вікі .

  • якщо n <1 373 653, досить перевірити a = 2 і 3;
  • якщо n <9080191, досить перевірити a = 31 і 73;
  • якщо n <4759123,141, досить перевірити a = 2, 7 і 61;
  • якщо n <2 152 302 898 747, досить перевірити a = 2, 3, 5, 7 та 11;
  • якщо n <3,474,749,660,383, досить перевірити a = 2, 3, 5, 7, 11 і 13;
  • якщо n <341,550,071,728,321, досить перевірити a = 2, 3, 5, 7, 11, 13 і 17.

Редагування 1 : Виправлений зворотний виклик if-elseдля додавання великих факторів до факторів primefactors.


Насолоджуйтесь своїм +100 (ви єдиний, хто відповів з моменту нагороди). Ваш bigfactors, проте, жахливо неефективний, тому що factors.extend(bigfactors(factor))повертається до великих факторів, що є просто невірним (що, якщо pollard-brent знайде фактор 234892, ви не хочете знову розкладати це на фактори з pollard-brent). Якщо ви перейдете factors.extend(bigfactors(factor))на factors.extend(primefactors(factor, sort))це, це добре.
orlp

Тоді один первинний фактор називає бігфактором, тоді стає зрозуміло, що потужність малого простого не буде в наступних факторах, отриманих за допомогою полярного бренту.
Розуур,

Якби його неефективно, я б не відповів на це. Після того, як виклик перейде з першофакторів на великі фактори, у n не буде коефіцієнта менше 1000, отже pollard-brent не зможе повернути число, коефіцієнти якого буде менше 1000. Якщо це не зрозуміло, відповідьте так, що я відредагую свою відповідь з додатковими поясненнями
Rozuur

Лайно NVM, звичайно. Якщо N не містить жодних факторів, виявлених малими праймами, тоді фактор F N також не буде>. <
orlp

Також слід використовувати smallprimeset замість smallprime і видалити smallprimeset з miller-rabin.
Розуур,

4

Навіть на нинішньому є кілька плям, на які слід звернути увагу.

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

Мені потрібно зробити checker * checker, оскільки число постійно зменшується. Однак я застосую пропуск парних чисел. Divmod значно зменшує функцію (він буде обчислювати // для кожного циклу, а не лише тоді, коли контролер ділить n).
orlp

@night, вам просто потрібно здійснити перерахунок, sколи ви змінюєтесь numтоді
John La Rooy

Правда, зрозумів, що, коли возиться :) Здається, швидше перерахувати sqrt, а не checker * checker.
orlp

@nightcracker: Нехай N=n*n+1, ceil(sqrt(N))коштує близько 2 ~ 4 рази більше , ніж n*n, numне змінюється , що часто.
Kabie

Чи знаєте ви про алгоритм sqrt для стелі / підлоги, тому що int (num ** .5) + 1 здається надмірним (спочатку обчисліть у точності з плаваючою точкою, а потім відріжте).
orlp

4

Існує бібліотека python з колекцією тестів на первинність (включаючи неправильні, щоб не робити). Це називається піпримами . Зрозумів, це варто згадати для нащадків. Я не думаю, що сюди входять алгоритми, про які ви згадали.



1

Ви можете розкласти на факторизми обмеження, а потім використовувати 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))

0

Я просто зіткнувся з помилкою в цьому коді при розрахуванні числа 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)і т.д. і т.п., коли всі ми піклуємося про «роблять [квадрат з] шашки перевищує n».

Переписаний код не створює винятків, якщо він представлений великими цифрами, і приблизно вдвічі швидший відповідно до timeit(на вхідних зразках 2та 2**718 * 31).

Також зверніть увагу, що isprime(2)повертається неправильний результат, але це нормально, якщо ми на нього не покладаємось. IMHO, вам слід переписати вступ цієї функції як

if n <= 3:
    return n >= 2
...
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.