牟曉東
印度有個古老傳說:舍罕王打算獎賞國際象棋的發明人——西薩宰相,在被問及想要得到的賞賜時,西薩回答說:“在棋盤的第1格放1粒大米,第2格放2粒,第3格放4粒,之后的每一格中的米粒數目都是相鄰前一格的兩倍,一直放到最后的第64格,我只要這一棋盤的大米。”
最初國王不以為意,但最終的結果卻是舉全國之力都無法填滿這個棋盤。果真是這樣嗎?我們使用Python編程來解決這個“棋盤米粒倍增”問題。
1.常規的循環求和法
首先通過“sum = 0”語句建立并為變量sum賦值為0,準備存放最終的米粒數目;接著使用for循環:“for i in range(64):”,其中的range()函數負責提供從0到63共64個循環計數;由于每格中米粒的數目可表示為“2的(n-1)次方”,所以循環體語句為“sum += 2 ** i”,將每次循環得到的該格子中米粒的數量與之前所有格子中米粒的數量和進行求和;循環結束后通過print語句將求和結果輸出。
將程序保存為chessrice1.py,運行后得到結果(如圖1):
棋盤米粒的總數為:184467440737
09551615 粒。
2.使用列表推導式計算
Python的列表推導式在邏輯上等同于循環語句,優點是形式簡潔且速度快,它能夠以非常簡潔的方式對列表(或其他可迭代對象)中的元素進行遍歷、過濾或再次計算,從而快速生成滿足特定需求的列表。
Python的列表推導式可分解為“表達式+循環”兩部分,比如通過“sum = sum([2**i for i in range(64)])”這一個語句即可完成所有64格子中米粒的數量求和,其中的“2**i”即“表達式”部分,作用是計算每格中的米粒數量;后面的“for i in range(64)”是“循環”部分,作用是控制完成從0到63共64次循環;sum變量的賦值,是通過內置求和sum()函數來完成的。
之前使用常規循環求和法得到的結果是一個20位長的天文數字,單位是“粒”,不夠直觀。經查詢,1千克大米約有52000粒,通過“mass = int(sum / 52000000)”語句,將這些大米的數目轉換成單位為“噸”并進行求整,賦值給mass變量,最后打印輸出。
將程序保存為chessrice2.py,運行后得到結果(如圖2):
棋盤米粒的總數為:184467440737
09551615 粒。
這些米粒的總質量為:3547450783
40 噸。
米粒總數的計算結果與循環求和法一致,它們的總質量是個12位數字,約是3547.5億噸!中國目前每年產大米約2億噸,所以國王無論如何也拿不出數量如此龐大的大米,根本就填不滿宰相的棋盤。
3.兩種方法打印“九九乘法表”
不管是使用常規循環求和還是使用列表推導式,我們都可以正確求解“棋盤米粒倍增”問題,二者在各種問題的求解過程中都比較方便,包括循環的嵌套,比如打印“九九乘法表”。
(1)常規的雙層循環嵌套
外層循環語句為“for i in range(1,10):”,作用是從1到9循環;內層循環語句為“for j in range(1,i+1):”,同樣是使用range()進行對應次數的循環;循環體語句為“print(‘{0}*{1} = {2}.format(j,i,i*j),end=‘ ) ”,這個print語句用到了Python的format()方法進行字符串格式化,其中的“{0}”、“{1}”和“{2}”是位置參數,作用是將后面“format(j,i,i*j)”中的三個變量的對應數值進行占位輸出;“end=‘ ”的作用是設置末尾不換行,而不是print的默認“換行”值;內層循環結束后是一個“print()”空語句,作用是換行,即打印完同一個乘數(比如同是乘以3)的一行循環后,回車換行。
將程序保存為ninenine1.py,運行后得到“九九乘法表”(如圖3)。
(2)列表推導式循環嵌套
外層循環語句仍為“for i in range(1,10):”,內層直接就是一個列表推導式(因為本身就是一層循環):“print(“”.join([“%d*%d=%-2d”%(j,i,j*i) for j in range(1,i+1)]))”。這個print語句中的“join()”方法是將序列中的元素以指定的字符連接生成一個新字符串,依次連接到前面的“”空串后面;其中的“%d”的作用是將數據按照整型格式化輸出,“-”表示左對齊,“2”表示數字不足兩位時進行位數補齊(不足位置用空格)。列表推導式后面的循環部分是“for j in range(1,i+1)”語句,與常規雙層循環嵌套的內層循環語句完全相同。
將程序保存為ninenine2.py,運行后,同樣也得到了“九九乘法表”(如圖4)。