解决拿蛋问题的时候,通过几个shell脚本运算速度对比,体会了算法和编程优化的重要性

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了解决拿蛋问题的时候,通过几个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脚本运算速度对比,体会了算法和编程优化的重要性的主要内容,如果未能解决你的问题,请参考以下文章

分享嵌入式中几个实用的shell脚本!

Shell脚本自动部署(编译)LAMP平台

Linux Shell脚本编程-基础1

shell脚本基本应用特殊的Shell变量

shell中的let vs expr vs双括号算术

Shell 脚本浮点运算