標籤:拿蛋問題 shell指令碼運算速度對比 演算法和編程最佳化
前幾天,一位同學在群裡提出一個拿蛋的問題,原題如下:
有一筐雞蛋,
1個1個拿,正好拿完
2個2個拿,正好拿完
3個3個拿,正好拿完
4個4個拿,剩下2個
5個5個拿,剩下4個
6個6個拿,正好拿完
7個7個拿,剩下5個
8個8個拿,剩下2個
9個9個拿,正好拿完
求:筐裡一共有多少雞蛋?
請使用指令碼方式,計算雞蛋總數!
個人感覺這個題目寫的不嚴謹,因為至少我沒看明白,這道題問的到底是“這個筐裡最少有多少雞蛋?”還是“筐裡雞蛋總數在某一範圍之內(比如這個筐裡最多能裝100000個蛋的前提條件下),求筐裡雞蛋有可能是多少個?”暫且先按前一種猜測解答這道題吧,第二種猜測後續做為變種擴充一下。
其實這個問題貌似比較常見了,在網上隨隨便便就能搜出答案,但是不經思考拿來就用不理解消化成自己的知識的話,下次遇到相同或者類似的問題仍然不會,再加上正好培訓班的老師這段時間也正在講Shell進階編程,就拿這個題目來練習一下Shell編程吧。
首先想到的是簡單粗暴暴力破解型的指令碼:一個數一個數算唄!有啥難的?
代碼1(簡單粗暴版):
#!/bin/bashecho -e "有一筐雞蛋,\n1個1個拿,正好拿完\n2個2個拿,正好拿完\n3個3個拿,正好拿完\n4個4個拿,剩下2個\n5個5個拿,剩下4個\n6個6個拿,正好拿完\n7個7個拿,剩下5個\n8個8個拿,剩下2個\n9個9個拿,正好拿完\n求:筐裡一共有多少雞蛋?\n請使用指令碼方式,計算雞蛋總數!\n"for i in {1..100000}do if [[ $i%1 -eq 0 ]] && [[ $i%2 -eq 0 ]] && [[ $i%3 -eq 0 ]] && [[ $i%4 -eq 2 ]] && [[ $i%5 -eq 4 ]] && [[ $i%6 -eq 0 ]] && [[ $i%7 -eq 5 ]] && [[ $i%8 -eq 2 ]] && [[ $i%9 -eq 0 ]] then echo $i break fidone
順便使用time命令測試了一下指令碼運行消耗的時間:
第一次:
第二次:
第三次:
測試通過,沒問題,只是這代碼寫的太無腦了,簡直不忍直視,有沒有最佳化的空間呢?答案是肯定的,但這時候就需要有一點點邏輯思維的能力了(學過小學數學即可)。
思考如何最佳化:
1. 任何數都能被1整除,這個不用做判斷。
2. 可以被2、3、6、9同時整除,那麼因為2、3、6、9的最小公倍數是18,所以直接用18的倍數判斷能否除4餘2,除5餘4,除7餘5,除8餘2即可,這裡稍微需要注意是求餘不是除。
3. 隱藏條件:5個5個拿,剩下四個,那麼說明這筐雞蛋總數量的個位元要麼是4,要麼是9,但是由條件2(2個2個拿,正好拿完)得知,此數必為偶數,故個位元不能為9。結合此數必為18的倍數這個條件,那麼當此數為18的3+5×N倍時,即可同時滿足既是“18的倍數”且“個位元是4”這兩個條件。
思考結束後,就開始研究如何迴圈計算18的3+5×N倍能否除4餘2,除7餘5,除8餘2
代碼2(最佳化版):
#!/bin/bash############################################################### File Name: modified.sh# Version: V1.0# Author: Damon Pan# Blog: http://blog.51cto.com/oldpan# Created Time : 2018-04-17 15:00:28# Description: null##############################################################echo -e "有一筐雞蛋,\n1個1個拿,正好拿完\n2個2個拿,正好拿完\n3個3個拿,正好拿完\n4個4個拿,剩下2個\n5個5個拿,剩下4個\n6個6個拿,正好拿完\n7個7個拿,剩下5個\n8個8個拿,剩下2個\n9個9個拿,正好拿完\n求:筐裡一共有多少雞蛋?\n請使用指令碼方式,計算雞蛋總數!\n"for((i=0;i<=100000;i++))do ((n=18*(3+5*${i}))) if ((${n}%4==2)) && ((${n}%7==5)) && ((${n}%8==2)) then echo ${n} break fidone
同樣使用time命令測試指令碼運行消耗的時間:
第一次:
第二次:
第三次:
果然沒有對比,就沒有傷害!運行速度相差了30多倍!這還只是計算到1314就停止了的情況下。那麼如果把題目的要求修改一下呢?比如就像前文中我對這道題目的要求進行的第二種猜測一樣,把題目的要求改為:“如果這個筐裡最多能裝10萬個雞蛋,求這個筐裡分別有可能有多少個雞蛋。”
簡單粗暴型的代碼就是把前文中代碼1簡單粗暴版 裡的if迴圈中的break去掉即可,這裡不再重複列出代碼了,只把三次time運行指令碼的結果列在下面。
time測試第一次:
第二次:
第三次:
那麼最佳化後的指令碼運行速度如何呢?(代碼部分同樣只是把前文中代碼2最佳化版 裡的 if迴圈裡的break去掉,但是下面的測試證明這樣做其實犯了一個邏輯錯誤)
time測試:
這~~怎麼比沒有最佳化的指令碼用的時間還多了呢?而且算出來的數字已經超過10萬了。肯定是代碼有錯誤了,開始修改代碼
在 if 判斷中增加了一個條件,n不能大於10萬,這樣修改之後,輸出的結果倒是正確了,但是指令碼執行速度仍然很慢。
time測試第一次:
第二次:
第三次:
看來代碼還是有問題,回頭仔細查看代碼,發現雖然在if中增加了一條判斷,但是實際上for迴圈的運算次數一次都沒有減少,只是計算到10萬以後就不再顯示計算結果了。繼續修改代碼:
#!/bin/bash############################################################### File Name: finalVersion.sh# Version: V1.0# Author: Damon Pan# Blog: http://blog.51cto.com/oldpan# Created Time : 2018-04-17 16:06:27# Description: null##############################################################echo -e "有一筐雞蛋,\n1個1個拿,正好拿完\n2個2個拿,正好拿完\n3個3個拿,正好拿完\n4個4個拿,剩下2個\n5個5個拿,剩下4個\n6個6個拿,正好拿完\n7個7個拿,剩下5個\n8個8個拿,剩下2個\n9個9個拿,正好拿完\n求:筐裡一共有多少雞蛋?\n請使用指令碼方式,計算雞蛋總數!\n"for((i=0;i<=100000;i++))do ((n=18*(3+5*${i}))) if ((${n}>100000)) then break elif ((${n}%4==2)) && ((${n}%7==5)) && ((${n}%8==2)) then echo ${n} fidone
time測試第一次:
第二次:
第三次:
計算效率提高了40多倍,沒有預期的那麼大,可能我的程式邏輯還是有問題,應該還有可以進一步最佳化改進的地方。但是因本人數學和編程水平極其有限,再改就改不動了。。。還望有高手不吝賜教,在下先謝過了!
本次實驗通過同學提出的一個問題,驗證了演算法和編程最佳化的重要性,只是使用了一種最最簡單低級的最佳化,便把計算效率提高了40倍左右,如果需要計算的資料量非常大的時候,效率的提升還是很明顯的。
解決拿蛋問題的時候,通過幾個shell指令碼運算速度對比,體會了演算法和編程最佳化的重要性