1. 程式人生 > >解決拿蛋問題的時候,通過幾個shell腳本運算速度對比,體會了算法和編程優化的重要性

解決拿蛋問題的時候,通過幾個shell腳本運算速度對比,體會了算法和編程優化的重要性

拿蛋問題 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/bash

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 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
  fi
done

順便使用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
  fi
done


同樣使用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}
  fi
done


time測試第一次:

技術分享圖片

第二次:

技術分享圖片

第三次:

技術分享圖片


計算效率提高了40多倍,沒有預期的那麽大,可能我的程序邏輯還是有問題,應該還有可以進一步優化改進的地方。但是因本人數學和編程水平極其有限,再改就改不動了。。。還望有高手不吝賜教,在下先謝過了!


本次試驗通過同學提出的一個問題,驗證了算法和編程優化的重要性,只是使用了一種最最簡單低級的優化,便把計算效率提高了40倍左右,如果需要計算的數據量非常大的時候,效率的提升還是很明顯的。

解決拿蛋問題的時候,通過幾個shell腳本運算速度對比,體會了算法和編程優化的重要性