前几天,一位同学在群里提出一个拿蛋的问题,原题如下:
有一筐鸡蛋,
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" foriin{1..100000} do if[[$i%1-eq0]]&&[[$i%2-eq0]]&&[[$i%3-eq0]]&&[[$i%4-eq2]]&&[[$i%5-eq4]]&&[[$i%6-eq0]]&&[[$i%7-eq5]]&&[[$i%8-eq2]]&&[[$i%9-eq0]] then echo$i break fi done
顺便使用time命令测试了一下脚本运行消耗的时间:
第一次:
第二次:
第三次:
测试通过,没问题,只是这代码写的太无脑了,简直不忍直视,有没有优化的空间呢?答案是肯定的,但这时候就需要有一点点逻辑思维的能力了(学过小学数学即可)。
思考如何优化:(优化的核心思想可以通过【减少if判断的条件】和【减少for循环的次数】两个方面,最终来达到减少代码计算所需总时间的目的。所以下面对代码进行的各种优化,都是围绕这两个核心思想来进行的。)
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 ############################################################## #FileName:modified.sh #Version:V1.0 #Author:DamonPan #Blog:http://blog.51cto.com/oldpan #CreatedTime:2018-04-1715: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 ############################################################## #FileName:finalVersion.sh #Version:V1.0 #Author:DamonPan #Blog:http://blog.51cto.com/oldpan #CreatedTime:2018-04-1716: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倍左右,如果需要计算的数据量非常大的时候,效率的提升还是很明显的。