[英]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.