有一筐鸡蛋,
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脚本运算速度对比,体会了算法和编程优化的重要性
原文地址:http://blog.51cto.com/oldpan/2105388