簡體   English   中英

為什么這段 Python 代碼這么慢? 我怎樣才能讓它更有效率?

[英]Why is this Python code so slow? How can I make it more efficient?

我正在解決Project Euler 網站上的問題 #69 它是關於 Euler 的 Totient 函數的。 這個函數的定義可以在這里找到。 無論如何,問題要求在整數 2 到 1,000,000 上找到 n / Phi(n) 的最大值,其中 Phi 是 Totient 函數。 我知道我的代碼作品,它正確地找到N = 6為最大時的搜索間隔為2〜10。但是,這是窘況慢-使用1000的上限延伸的計算時間大致30秒這意味着使用 1,000,000 的上限大約需要至少 8 小時! 在 Project Euler 網站上,它指出在中等功率的計算機上,任何單個計算都不應超過一分鍾。 我的電腦很強大,所以在這方面沒有任何不足。 也許我正在使用的 Jupyter Notebooks IDE 的編譯器效率特別低? 我不太確定。 對此問題的幫助將不勝感激。 下面是我的代碼:

def FactorsOf(n,include_ends=False): #Returns the factors of a positive integer in an array.
    factors = []                     #The include_ends param can be set to True if 1 & n are to be
    a=0                              #included in the returned array.
    b=0
    if (include_ends):
        a=1
        b=n+1
    else:   
        a=2
        b=n
    for k in range(a,b):
            if (n%k==0):
                factors.append(k)
    return factors
def AreRelativelyPrime(a,b):
    a_factors=FactorsOf(a,include_ends=True)
    b_factors=FactorsOf(b,include_ends=True)
    for i in range(1,len(a_factors)):           #Searches through both factor arrays to see if there
        for j in range(1,len(b_factors)):       #are any elements in common. Of course the first element,
            if (a_factors[i]==b_factors[j]):    # 1, is excluded.
                return False
    return True        
def Totient(n):
    totient=1                       #The Totient function's minimum value is 1.
    n_factors = FactorsOf(n)
    for m in range(2,n):
        if(AreRelativelyPrime(n,m)):     #Increments the Totient function every time we find a 
            totient+=1                   # number relatively prime to n.
    return totient
n_over_phi_MAX = 2
maxes = []
for n in range(2,1001):
    n_over_phi = n/Totient(n)
    if(n_over_phi > n_over_phi_MAX):
        n_over_phi_MAX = n_over_phi
        maxes.append(n)
print("The maxiumum value of n/phi(n) is " + str(n_over_phi_MAX) + " at a value of " + str(maxes[-1]))

正如我測試的,如果我們緩存因子生成結果,整個過程會加快很多。

factors_cache = {}

def FactorsOf(n,include_ends=False):
    key = (n, include_ends)
    if key in factors_cache:
        return factors_cache[key]
    factors = []
    a=0
    b=0
    if (include_ends):
        a=1
        b=n+1
    else:   
        a=2
        b=n
    for k in range(a,b):
        if (n%k==0):
            factors.append(k)
    factors_cache[key] = factors
    return factors

這里不是 Python 的問題。 你的算法效率不高。 您需要使用歐拉 phi 函數的屬性。 其中之一是如果n = p1^a1 * p2^a2 * p3^a3 ... * pk^ak其中p1, p2, ... ,pk是素數, a1, a2, ..., ak是整數然后:

phi(n) = n * (1 - 1 / p1) * (1 - 1 / p2) * ... * (1 - 1 / pk)

參見: https : //en.wikipedia.org/wiki/Euler%27s_totient_function#Proof_of_Euler's_product_formula

這是使用此事實的更有效的代碼:

import math

def Totient(n): # Returns the factors of a positive integer in an array.
    phi = n
    for i in range(2, math.ceil(math.sqrt(n)) + 1):
        if n % i == 0:
            while n % i == 0:
                n /= i
            phi -= phi / i
    if n > 1:
        phi -= phi / n
    return phi

n_over_phi_MAX = 2
maxes = []

for n in range(2, 1001):
    n_over_phi = n / Totient(n)
    if n_over_phi > n_over_phi_MAX:
        n_over_phi_MAX = n_over_phi
        maxes.append(n)
print("The maximum value of n/phi(n) is " + str(n_over_phi_MAX) + " at a value of " + str(maxes[-1]))

在任何編程語言中,無論您如何編程,將每個整數從 2 分解為 1M 都需要很長時間。 您需要完全使用不同的算法。 一種更有效的方法可能是利用 phi 函數是乘法這一事實。 鑒於素數的冪的 phi 很容易計算: p**k - p**(k-1) ,您可以生成素數(使用 Eratosthenes 的篩子)並運行這些冪的所有倍數素數。 這些素數的任何乘數(它本身不是所述素數的倍數)的 gcd 為 1 和素數。 然后,phi 函數的乘法屬性允許基於先前計算的 phi 值(乘數和素數冪的)計算該倍數的 phi 值。

這在我的筆記本電腦上運行 0.5 秒。

下面是算法的一個例子:

N     = 1000000
phi   = [0]+[1]*N  
done  = [False]*(N+1)     
prime = [True]*(N+1)

for p in range(2,N+1):
    if not prime[p]: continue
    prime[p*p::p] = [False]*len(range(p*p,N+1,p)) # sieve of Eratosthenes
    n = p
    while n < N:                      # n is every power of prime p (in range of N)
        phi[n]  = n - n//p            # phi(n) for a power of a prime
        done[n] = True
        for m in range(2,N//n+1):     # Multipliers of n will have gcd(m,n) == 1
            if m%p == 0: continue     # if m not divisible by p
            if not done[m]: continue  # Expand from known phi(m)
            nm       = n*m
            phi[nm]  = phi[n]*phi[m]  # totient function is multiplicative
            done[nm] = True
        n *= p

# once you have the phi values for all numbers in range, 
# you can get the maximum ratio of n over Phi(n) 

maxN2Phi = max(range(2,N),key=lambda n:n/phi[n])

輸出:

# print(f"Max n/phi(n) from 1 to {N}: {maxN2Phi} / {phi[maxN2Phi]} ({maxN2Phi/phi[maxN2Phi]})") 

print("\nFIRST 143 phi(n):")
for b in range(0,144,12):
    print(" ".join(f"{n or '':4}" for n in phi[b:b+12])) 


FIRST 143 phi(n):
        1    1    2    2    4    2    6    4    6    4   10
   4   12    6    8    8   16    6   18    8   12   10   22
   8   20   12   18   12   28    8   30   16   20   16   24
  12   36   18   24   16   40   12   42   20   24   22   46
  16   42   20   32   24   52   18   40   24   36   28   58
  16   60   30   36   32   48   20   66   32   44   24   70
  24   72   36   40   36   60   24   78   32   54   40   82
  24   64   42   56   40   88   24   72   44   60   46   72
  32   96   42   60   40  100   32  102   48   48   52  106
  36  108   40   72   48  112   36   88   56   72   58   96
  32  110   60   80   60  100   36  126   64   84   48  130
  40  108   66   72   64  136   44  138   48   92   70  120

不按照歐拉項目規則在此處打印答案

另一種選擇是利用 phi(n) = n * (1-1/p) * (1-1/p) * ... 屬性。 這實現起來要簡單得多,但在我的測試中運行速度較慢(盡管仍低於 1 秒):

N     = 1000000
phi   = list(range(N+1))    
prime = [True]*(N+1)

for p in range(2,N+1):
    if not prime[p]: continue
    prime[p*p::p] = [False]*len(range(p*p,N+1,p))   # sieve of Eratosthenes
    phi[p::p]     = [ n - n//p for n in phi[p::p] ] # phi(n) = n*(1-1/p)*(1-1/p)...

maxN2Phi = max(range(2,N),key=lambda n:n/phi[n])

[編輯] 一個更快的解決方案

通過更接近目標(即 n/phi(n))而不是產生 phi 值,我們可以制定策略,最大比率將是n盡可能大而phi(n)盡可能小。

鑒於 phi(n) = n*(1-1/p)*(1-1/p)... n每個素n減少 phi(n) 的值。 此外,較小的素數比較大的素數減少更多。 因此,我們需要盡可能多的最小素數作為 n 的因數。 這意味着選擇所有第一個素數,直到它們的乘積大於 1,000,000。

此外,由於我們希望n盡可能大,一旦我們達到最大數量的素數,我們可以進一步將這些素數的乘積乘以 2 或 3,或 4 ...只要n保持低於1,000,000。

這種方法直接給出了只生成素數的解決方案,最多可達 1,000,000 的很小一部分。

該算法在 0.00005 秒(50 微秒)內生成解決方案

這是代碼:

N     = 1000000
prime = [True]*int(N**0.5) # largest prime used will be smaller than square root of N           
n     = 1

for p in range(2,len(prime)):
    if not prime[p]: continue
    prime[p*p::p] = [False]*len(prime[p*p::p]) # Eratosthenes
    if n*p < N:  n *= p                        # product of first primes
    else: break                                # while product fits within N

n   = n*(N//n)                                 # multiply to maximize n within N
phi = n                                        # compute phi(n)
for f in range(2,p):
    if prime[f]: phi -= phi//f                 # n*(1-1/p)(1-1/p) ...

if printIt:
    print(f"Max n/phi(n) from 1 to {N}: n={n} phi(n)={phi} ({n/phi})") 

暫無
暫無

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

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