昨天群里有人貼了下面這張圖片,勾起了大家興趣,引發了大家的討論。

雖然明白求素數的原理,但還沒想明白程序如何寫時,有人貼了如下這段代碼。

因為最近在學 python ,且對其中2個循環的邊界上限為什么這樣寫沒想明白,於是把這個程序翻譯成 python 版。
下面是我的程序:
import time start = time.clock() def panduan(n): # strn = str(n) # if strn[-1] not in ('1', '7', '3', '9'): # 注1 # return False for m in range(2, int(n / 2)): # 注2 if (n % m == 0): return False return True m = m2 = False i = 707829217 for n in range(3, int(i ** 0.5)): # 注3 if (i % n == 0): m = panduan(n) m2 = panduan(int(i / n)) # 注4 if (m & m2): break # print(n, int(i / n), sep = '\t') print('微信號:NY' + str(int(i/n))+str(n)) end = time.clock() print('耗時:{}'.format(end - start))
解析:1)java中“ for(i = 1; i < 10; i++) ”這樣的循環,python中通過“ for i in range(10) ”來變相的實現。
但java中,邊界上限可以是浮點數等非整數;而range函數中,參數必須是整數。
所以,2個循環中,向range函數傳值時,使用int函數將計算值強制轉換為整數。
2)注2,一開始並不明白,為什么要“n/2”,而不直接使用n。
現在認為,這是為了效率優化。因為m作為n的除數,如果n不是質數,那么在“n/2”范圍內一定能找到可以整除n的數。
3)注3,原文中,這里的邊界上限使用的是“i/3”,這也是一開始讓我感到困惑的地方,同樣不明白為什么不使用i,並且i的1/3又是如何得來的?
現在看來,也是效率優化。因為題目中很明確的提示了兩個乘數有大小,所以這2個數肯定不會大於i的1/2。
至於進一步取到1/3,可能就定的比較隨意了,至少原作者認為較小數在1/3內一定出現。或許有更明確的原因,但我還沒想明白。
想明白這一點后,既然i是乘積,那么在i的開方中,一定能找到這個較小數。
所以我把上限改為了i的開方。點擊這里,可以看python中開方的3種寫法。
4)注1,原文中沒有這個判斷。分析數值“707829217”,因為個位數為7,所以2個乘數的個位數一定是7和1、或3和9(感謝 @メ﹎僋翫 指出原代碼中的一個疏漏)。
因此為了提升效率,增加了一個傳入值n的個位數是否為1或7、3、9的判斷,如果不是直接結束本次函數調用。
(因為 注4 的修改,導致 注1 的修改提升的效率不大,新版本中注釋掉了這段代碼。)
需要注意的是,python中“i/n”是一個浮點數(即使 i 可以被 n 整除)。
所以開始增加注1的判斷后,因為得到的2個數是非質數,追查發現因為傳入值n是浮點數,所以導致該判斷未實現預期效果。
解決方法,在傳入函數前,先將“i/n”強制轉換成整數。
同樣道理,在最后的輸出2個質素時,給“i/n”增加int轉化,也是因為不轉換的話,輸出是浮點數。
5)注4,原文中該行代碼不在 if語句 中,相當於每次for循環都要執行。
但只有在“i/n”是整數的情況下,判斷該數是否素數才有有意義,所以放入 if語句(即 i 能被 n 整除時) 中更合適。
6)因為好奇,注1的優化增加后,雖然能感到和原程序比,效率上提高了很多,但是不知道提高了多少,所以對程序又做了改造。
通過 time.clock() 在程序開始和結束時各獲取一次時間,然后對2者求差,已得到每次的程序運行時間。
幾種求程序運行時間的方法,請參考這里。
a)按原文翻譯后,程序耗時 28.7040 秒左右;
b)在此基礎上,增加 注1 的判斷,耗時縮短為 12.7571 秒左右;
c)這時,做 注4 的修改,並暫時注釋掉 注1,奇跡發生了!耗時在 0.0133秒 左右波動(大多落在 0.013~0.016 范圍內);
在 注1 被注釋的情況下,這里傳參數值時,不轉換成 int 型也可以,但觀察耗時平均會多 0.001 秒。
原因不是很理解,可能和 int 、float型 的存儲機制有關。
d)那么,這時 注1 還能提升效率嗎?恢復后,雖然有一次耗時能達到 0.0126秒 ,但多數時間和(c)項比,時間上並沒有更優。
所以最終版本,注1 被注釋掉了。
