簡體   English   中英

使用 GMPY2(或 GMP)查找所有因素的最有效方法?

[英]Most efficient way to find all factors with GMPY2 (or GMP)?

我知道已經有一個與此類似的問題,但我想使用 GMPY2(或與 GMP 類似的東西)加快速度。 這是我當前的代碼,它很不錯,但可以更好嗎?

編輯:新代碼,檢查除數 2 和 3

def factors(n):
    result = set()
    result |= {mpz(1), mpz(n)}


    def all_multiples(result, n, factor):
        z = mpz(n)
        while gmpy2.f_mod(mpz(z), factor) == 0:
            z = gmpy2.divexact(z, factor)
            result |= {mpz(factor), z}
        return result

    result = all_multiples(result, n, 2)
    result = all_multiples(result, n, 3)

    for i in range(1, gmpy2.isqrt(n) + 1, 6):
        i1 = mpz(i) + 1
        i2 = mpz(i) + 5
        div1, mod1 = gmpy2.f_divmod(n, i1)
        div2, mod2 = gmpy2.f_divmod(n, i2)
        if mod1 == 0:
            result |= {i1, div1}
        if mod2 == 0:
            result |= {i2, div2}
    return result

如果可能的話,我也對只在n^(1/3) and 2^(2/3)*n(1/3)內的除數實現感興趣

例如,mathematica 的factor()比 python 代碼快得多。 我想分解 20 到 50 個十進制數字之間的數字。 我知道 ggnfs 可以在不到 5 秒的時間內分解這些。

我對 python 中是否也存在任何實現快速分解的模塊感興趣。

我剛剛對您的代碼進行了一些快速更改,以消除多余的名稱查找。 該算法仍然相同,但是在我的計算機上大約是它的兩倍。

import gmpy2
from gmpy2 import mpz

def factors(n):
    result = set()
    n = mpz(n)
    for i in range(1, gmpy2.isqrt(n) + 1):
        div, mod = divmod(n, i)
        if not mod:
            result |= {mpz(i), div}
    return result

print(factors(12345678901234567))

其他建議將需要有關數字大小等的更多信息。例如,如果您需要所有可能的因素,則從所有主要因素中構造這些因素可能會更快。 該方法將使您在繼續操作時減小range語句的限制,也可使您增加2(在刪除所有2的因子之后)。

更新1

我對您的代碼做了一些其他更改。 我認為您的all_multiplies()函數不正確。 您的range()語句不是最優的,因為再次檢查了2,但是我的第一個修復使情況變得更糟。

新代碼會延遲計算輔因子,直到知道余數為0。我還嘗試了盡可能多地使用內置函數。 例如, mpz % integer比gmpy2.f_mod(mpz,integer)或gmpy2.f_mod(integer,mpz)快,其中integer是普通的Python整數。

import gmpy2
from gmpy2 import mpz, isqrt

def factors(n):
    n = mpz(n)

    result = set()
    result |= {mpz(1), n}

    def all_multiples(result, n, factor):
        z = n
        f = mpz(factor)
        while z % f == 0:
            result |= {f, z // f}
            f += factor
        return result

    result = all_multiples(result, n, 2)
    result = all_multiples(result, n, 3)

    for i in range(1, isqrt(n) + 1, 6):
        i1 = i + 1
        i2 = i + 5
        if not n % i1:
            result |= {mpz(i1), n // i1}
        if not n % i2:
            result |= {mpz(i2), n // i2}
    return result

print(factors(12345678901234567))

我將更改程序,以找到小於n平方根的所有素因數,然后再構造所有輔助因數。 然后你減少n每次發現一個因素,檢查是否n是首要的,只有尋找更多的因素,如果n不是素數。

更新2

Pyecm模塊應該能夠分解您嘗試分解的尺寸編號。 以下示例將在大約一秒鍾內完成。

>>> import pyecm
>>> list(pyecm.factors(12345678901234567890123456789012345678901, False, True, 10, 1))
[mpz(29), mpz(43), mpz(43), mpz(55202177), mpz(2928109491677), mpz(1424415039563189)]

互聯網上存在不同的 Python 因子分解模塊。 但是,如果您想自己實現因式分解(不使用外部庫),那么我可以建議非常快速且非常容易地實現Pollard-Rho 算法 我在下面的代碼中完全實現了它,如果您不想閱讀,只需直接向下滾動到我的代碼(在答案底部)。

Pollard-Rho 算法很有可能在O(Sqrt(P))時間內找到最小的非平凡因子P (不等於1N O(Sqrt(P)) 為了進行比較,您在問題中實現的Trial Division算法需要O(P)時間來找到因子P 這意味着例如,如果一個質因數P = 1 000 003那么試除會在1 000 003除法運算后找到它,而 Pollard-Rho 平均會在1 000運算后找到它( Sqrt(1 000 003) = 1 000 ),這要快得多。

為了使 Pollard-Rho 算法更快,我們應該能夠檢測素數,將它們從因式分解中排除,並且不要等待不必要的時間,因為在我的代碼中,我使用了費馬素性測試,它非常快速且易於實現7-9 行代碼。

Pollard-Rho 算法本身很短,13-15 行代碼,你可以在我的pollard_rho_factor()函數的最底部看到它,其余的代碼行是補充的helpers-functions。

我從頭開始實現了所有算法,沒有使用額外的庫( random模塊除外)。 這就是為什么你可以在那里看到我的gcd()函數的原因,盡管你可以使用內置的 Python 的math.gcd()代替(它找到Greatest Common Divisor )。

您可以在我的代碼中看到函數Int() ,它僅用於將 Python 的整數轉換為GMPY2 GMPY2 ints 將使算法更快,您可以使用 Python 的int(x)代替。 我沒有使用任何特定的 GMPY2 函數,只是將所有整數轉換為 GMPY2 整數以獲得大約 50% 的加速。

例如,我分解Pi 的前 190 位數字!!! 分解它們需要 3-15 秒。 Pollard-Rho 算法是隨機的,因此在每次運行時分解相同數字需要不同的時間。 您可以再次重新啟動程序,看到它會打印不同的運行時間。

當然,分解時間在很大程度上取決於質數除數的大小。 一些 50-200 位數字可以在幾分之一秒內分解,有些則需要數月時間。 我的示例 Pi 的 190 位數具有非常小的素因數,除了最大的素因數,這就是它很快的原因。 Pi 的其他數字可能沒有那么快分解。 所以數字的數字大小並不重要,只有質因數的大小很重要。

我特意將pollard_rho_factor()函數實現為一個獨立的函數,而不是將其分解為更小的獨立函數。 盡管它打破了 Python 的風格指南,它(我記得)建議不要使用嵌套函數並將所有可能的函數放在全局范圍內。 樣式指南還建議在腳本的第一行中在全局范圍內執行所有導入。 我特意做了一個函數,所以它很容易復制粘貼,並且完全可以在你的代碼中使用。 Fermat 素性測試is_fermat_probable_prime()子函數也是可復制粘貼的,無需額外依賴即可工作。

在極少數情況下,Pollard-Rho 算法可能無法找到非平凡的質因數,尤其是對於非常小的因數,例如,您可以將test() n替換為小數4並看到 Pollard-Rho 失敗。 對於如此小的失敗因素,您可以輕松使用您在問題中實施的Trial Division算法。

在線試試吧!

def pollard_rho_factor(N, *, trials = 16):
    # https://en.wikipedia.org/wiki/Pollard%27s_rho_algorithm
    import math, random
    
    def Int(x):
        import gmpy2
        return gmpy2.mpz(x) # int(x)
    
    def is_fermat_probable_prime(n, *, trials = 32):
        # https://en.wikipedia.org/wiki/Fermat_primality_test
        import random
        if n <= 16:
            return n in (2, 3, 5, 7, 11, 13)
        for i in range(trials):
            if pow(random.randint(2, n - 2), n - 1, n) != 1:
                return False
        return True
    
    def gcd(a, b):
        # https://en.wikipedia.org/wiki/Greatest_common_divisor
        # https://en.wikipedia.org/wiki/Euclidean_algorithm
        while b != 0:
            a, b = b, a % b
        return a
        
    def found(f, prime):
        print(f'Found {("composite", "prime")[prime]} factor, {math.log2(f):>7.03f} bits... {("Pollard-Rho failed to fully factor it!", "")[prime]}')
        return f
        
    N = Int(N)
    
    if N <= 1:
        return []
    
    if is_fermat_probable_prime(N):
        return [found(N, True)]
    
    for j in range(trials):
        i, stage, y, x = 0, 2, Int(1), Int(random.randint(1, N - 2))
        while True:
            r = gcd(N, abs(x - y))
            if r != 1:
                break
            if i == stage:
                y = x
                stage <<= 1
            x = (x * x + 1) % N
            i += 1
        if r != N:
            return sorted(pollard_rho_factor(r) + pollard_rho_factor(N // r))
    
    return [found(N, False)] # Pollard-Rho failed

def test():
    import time
    # http://www.math.com/tables/constants/pi.htm
    # pi = 3.
    #     1415926535 8979323846 2643383279 5028841971 6939937510 5820974944 5923078164 0628620899 8628034825 3421170679
    #     8214808651 3282306647 0938446095 5058223172 5359408128 4811174502 8410270193 8521105559 6446229489 5493038196
    # n = first 190 fractional digits of Pi
    n =   1415926535_8979323846_2643383279_5028841971_6939937510_5820974944_5923078164_0628620899_8628034825_3421170679_8214808651_3282306647_0938446095_5058223172_5359408128_4811174502_8410270193_8521105559_6446229489
    tb = time.time()
    print('N:', n)
    print('Factors:', pollard_rho_factor(n))
    print(f'Time: {time.time() - tb:.03f} sec')
    
test()

輸出:

N: 1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679821480865132823066470938446095505822317253594081284811174502841027019385211055596446229489
Found prime factor,   1.585 bits...
Found prime factor,   6.150 bits...
Found prime factor,  20.020 bits...
Found prime factor,  27.193 bits...
Found prime factor,  28.311 bits...
Found prime factor, 545.087 bits...
Factors: [mpz(3), mpz(71), mpz(1063541), mpz(153422959), mpz(332958319), mpz(122356390229851897378935483485536580757336676443481705501726535578690975860555141829117483263572548187951860901335596150415443615382488933330968669408906073630300473)]
Time: 2.963 sec

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM