Scratch用循环的方法实现斐波那契数列编程心得
1 斐波那契数列
1.1 定义
首先引用秒懂百科里面关于“斐波那契数列”的定义,详见“https://baike.baidu.com/item/斐波那契数列/99145?fr=aladdin”
斐波那契数列(Fibonacci sequence),又称黄金分割数列、因数学家列昂纳多·斐波那契(Leonardoda Fibonacci)以兔子繁殖为例子而引入,故又称为“兔子数列”,指的是这样一个数列:1、1、2、3、5、8、13、21、34、……在数学上,斐波纳契数列以如下被以递归的方法定义:F(0)=1,F(1)=1, F(n)=F(n-1)+F(n-2)(n>=2,n∈N*)在现代物理、准晶体结构、化学等领域,斐波纳契数列都有直接的应用,为此,美国数学会从1963年起出版了以《斐波纳契数列季刊》为名的一份数学杂志,用于专门刊载这方面的研究成果。
1.2 工具
本列采用Scratch软件进行演示。
1.3 本文的目的
1、通过创建斐波那契数列、绘制斐波那契弧线、绘制斐波那契矩形框来掌握循环、列表、绘图工具、自定义积木的使用方法;
2、学习在程序中应用三角函数、卡迪尔平面坐标等数学知识;
3、学习如何调试程序、检查程序、验证等方法,进一步加强对软件基础知识的掌握和理解。
4、本文不对代码的构建步骤进行记录,但会对一些代码进行解释。
5、探索文件管理、软件版本管理的方法。
1.4 效果
通过编程最终能达到的效果是正确计算出斐波那契数列,绘制出斐波那契弧线和矩形框,并且可以通过调整一些参数实现动态演示的效果。如下图所示
2 文件管理
2.1 文件管理
由于我是做资料的,所以对文件管理比较重视。通过这个程序,也简单的引入我对文件管理的一些理念。
2.2 文件管理的必要性
我们编写文稿、程序时都会生成一个或一组文件,使用期间可能还会不断的改进、删减、增加;有的时候又想保留一个现有版本…….总之很容易把文件数量、版本越搞越多。如果不认证对待,最后就会一塌糊涂。
2.3 简单的文件管理办法
1、为文件分类。不同的文件放在不同的文件夹下面,相同项目的子文件夹、文件放在同一个父文件夹里面。形成一个父子结构(也可以说是树状结构)。
2、为文件夹和文件取一个简单明了的名字。名字要取好,如果以后找文件自己都靠猜,效率如何提高,别人如何看得懂?没有一个好的名字,也就放弃了“搜索”这个有用的工具。
3、为文件夹和文件加上版本信息。虽然windows系统能够为文件添加一些备注信息(右键文件-属性-详细信息),编辑和查看不太方便。所以我的方法是直接修改文件名或者文件夹的名字。格式是:日期_文件名_版本号。
如
同一个版本的创建日期是不变的,如果该版本里面有少量的改动,可以先备份原文件,然后在新文件的版本后增加一个后缀,如V01_01,V01_02以示区别。
如果变动比较大,可以修改版本号,同时修改新版本的创建日期。如180520斐波那契数列(循环)V02_01。
好了,文件的管理暂且讲到这里,这个话题足够码很多字。接下来进入正题。
3 编程思路
3.1 递归还是循环
通过观察斐波那契数列,很容易看出除了第1位、第2位数是固定值以外,其他的数都是与前两位数相关联的。数学公式如下:
F(0)=1,F(1)=1, F(n)=F(n-1)+F(n-2)(n>=2,n∈N*)
我想到了有递归和循环两种方法可以实现。首先尝试用递归去实现,结果能力不济,折腾了1个小时没有进展,暂且搁置。
好在这个数列结构简单,用循环做起来也还方便,所以我决定先用循环的方法来实现。
3.2 程序的架构
本列内容不多,但是麻雀虽小五脏俱全,值得用心考虑程序的架构。
一开始我就犯了新手常见的毛病,所有功能写在一堆,程序乱作一团。这样的好处是程序不用加密,别人也看不明白。新手和高手之间只隔了一层纸,一上手就是搞加密工作的。
玩笑开过了,来看看我刚开始的代码吧,把计算数列、绘图掺和在一起,效果是这样的(此图不用去研究代码的逻辑关系和正确性)。
这样的结果是修改计算数列的代码可能会动到绘图的代码,修改绘图的代码可能会动到计算数列的代码。而且每次阅读都需要看很多不必要的代码,容易造成思维混乱。
怎么办呢?突然想起李泽老师的一节课《动态正弦波图形》,详见:https://www.cctalk.com/v/15022473793790。里面提到了绘图和角色操作分离的思想。于是我把计算斐波那契数列的代码放在一个角色中,绘制斐波那契数列曲线和矩形框的代码放在第二个角色中。这样代码就独立了。如下:
4 计算斐波那契数列
计算斐波那契数列与数列的级数有关,我把级数设置为一个变量。把该变量显示在舞台上,右键设置其为滑杆,再右键设置取值范围从1到20。
计算出来的数列在绘图时需要使用,同时为了方便观察、检验程序,我把数列存在列表中。
为了动态的调整斐波那契数列的级数,需要不停的采集参数“级数”的当前值,所以采用了一个重复执行循环。
计算斐波那契数列的代码如下:
计算结果如下
4.1 小技巧
1、自定义积木勾选“运行时不刷新屏幕”。这样的好处是防止大量运算的时候,屏幕会出现闪缩,积木会立即给你展示一个结果,而不会展示这个过程。
如果想看程序运行的步骤,可以不勾选这个选项,比如画圆的时候,你会看见画圆的整个过程。如下图:
2、把数据(参数)显示在舞台上,并且设置为滑杆,可以在程序运行期间动态的控制程序。如下图:
5 绘制斐波那契弧线和矩形框
5.1 观察
斐波那契弧线什么样子呢,先来看看网上的图案。
分析一下,主要有以下几个特点:
1、绘制斐波那契弧线和矩形框是由一系列的圆弧和矩形框组成的。每个矩形框包括一个90°的扇形,其半径等于矩形框的边长。
2、每段弧线和矩形框为一组,正好对应了斐波那契数列里面的数字和级数。
3、其他特点只可意会不可言传。数学不好,语文也差,我实在描绘不出啥了。更多的特点有兴趣的可以百度深入了解一下。
5.2 拆分
既然图形包括圆弧和矩形两部分,那么就分开来处理,这样便于思考和调试。暂且称为斐波那契弧线和斐波那契矩形框吧。
5.3 半径和比例尺
既然知道了圆弧的半径等于斐波那契数列的值,且每旋转90°就变一次半径就可以了(使用下一个斐波那契数列值作为下一段圆弧的半径),那太简单了。摩拳擦掌准备写代码了!先别忙,这里有个问题需要处理。
我们计算出来的斐波那契数列是这样的一系列数据:1、1、2、3、5、8、13、21、34、55……。倘若直接拿他们做为半径,那么前面7级的数列值都小于13,在舞台上画出来的效果可能就是一个点或者一小团,根本看不出效果,也检查不了程序是否正确。于是我引入了比例尺这个概念。半径r=数列值*比例尺,当级数较小的时候,可以通过改变比例尺的数值,改变半径r的值。
把比例尺可以放在舞台上,设置成滑杆,调整非常方便,实现了放大缩小的基本功能。如下图:
5.4 编写代码
由于之前没有准备写心得,所以中间过渡的代码没有保存。这里直接放一个最终的代码,截图如下。
有兴趣的朋友可以到网易卡搭看看我的程序,有源码哦。网址如下:https://kada.163.com/project/619941-954149.htm。
5.5 代码解读
5.5.1 程序架构
通过观察,每个数值对应1段弧线和1个矩形框,所以在每次循环中绘制1段弧线和1个矩形框。
5.5.2 等待0.2秒
一开始加入这条指令,是因为我觉得计算斐波那契数列和绘制图形是分开的两段代码,并且是同步运行的。为了防止绘图的时候,数列还没有建立起来,导致绘图错误,所以加入了这条等待指令。
通过测试,发现这条指令效果并不理想,因为绘图程序不停的从头到尾在刷新,且斐波那契系数每一级的数值是固定的。即便某个大循环绘制错误了,在下一个循环到来时,也可以修正。
那么这条指令是否就多余呢?
其实不一定。我们在做单片机或者自控系统开发的时候,在系统刚上电进行初始化设置的时候需要等待一段时间,让所有的元器件都能得到稳定的电压,达到正确的工况后程序再运行,可以避免一些意想不到的错误,保证设备安全运行。就跟早上起来发动汽车,需要原地怠速一小段时间,等润滑系统、电器系统正常工作以后再挂挡行驶是一样的。
5.5.3 找圆心
绘制一段圆弧,需要知道几个条件。包括:圆心、半径、角度。
其中半径等于斐波那契数列的数值*比例尺,角度等于90°。那么圆心在哪里?仔细观察一下网上的图形,会发现圆心是在变动的。如果不处理好,和可能出现这样的情况:
可能有的朋友在尝试写这段代码的时候出现过类似的情况。每一段弧线并没有衔接在一起。原因是绘制每一段弧线的圆心没有找对。
我的解决办法是记录上次结束时的位置坐标(oldx,oldy),在绘制下一段圆弧之前,计算出偏移量,然后调整圆心坐标。
代码如下:
5.5.4 画圆弧
有了圆心、半径、角度三个参数,就可以编辑绘制圆弧的代码了。程序如下:
落笔的x坐标、y坐标是通过三角函数计算得到的,计算时不要忘记了圆心是偏移了的哦。x坐标和y坐标都要加上圆心的坐标值。
为什么循环是91次?
因为第1次我们只是找到了起点,什么也没有画。
为什么最后要把角度减1?
经过91次循环后,画笔本应该转动90°。仔细观察程序,发现每绘制一小段弧线,角度会增加1°。可是由于代码的原因,在循环结束后,角度值是等于91°的。我们需要修正这1°的误差,因为接下来要绘制矩形框,需要保证画笔落在正确位置上,也就是圆弧的末端,而不是超前了1°。如何验证其重要性?很简单,删掉这条指令我们可以看看效果。如下:
5.5.5 画矩形框
矩形框的边长等于斐波那契数列的数值*比例尺。
如何绘制?
可以找到4个顶点坐标,然后逐一连接起来就搞定了。而且我们已经有了圆弧起点坐标、圆弧终点坐标、圆心坐标,剩下一个坐标通过几何计算就可以得到了。这个方法看似简单,但是对于像我这样几何基本还给老师的人来说有点折腾。
那就用更简单的办法,右拐右拐右拐再右拐。这个方法的难点是要知道首次右拐面向的方向。
为了把问题简单化,以1级数列来进行观察。
正确的情况是这样的,如下:
绘制矩形框的代码可能是这样的,如下
怎么确定刚开始的方向呢?四条边都画出来,看不出先后顺序啊?那就少画一条边吧,这样就知道起始方向了。效果如下:
我想大家能看出来,是从圆弧的下端沿顺时针方向绘画的了吧。
继续观察,在初始角度面向0°的情况下,每一级数列对应的圆弧终点都正好落在90°的整数倍方向上。所以绘制矩形框的初始方向就可以确定了,如下:
5.5.6 动态修改参数
在编辑代码的时候,设置了一些列的参数,如:级数、初始角度、圆心X、圆心Y、放大系数、旋转角速度。把他们放到舞台上,设置成滑杆,就可以在程序运行期间动态修改参数,实现平移、放大缩小、旋转等效果了。
6 其他
6.1 91°引起的疑问
第一版绘制弧线的代码在编辑过程中,除了代码凌乱以为,91°的问题也一直没有处理好,以至于出现了矩形框有重叠和位移的情况,而且这个偏差很小很小,级数越大越明显。如下图所示。
虽然怀疑过是91°的问题,但是当时没有调整好代码,以至于问题一直得不到解决。我便开始怀疑圆弧绘制的线条是通过三角函数计算出来的,有小数点的误差,导致画出来的圆弧到圆心的距离不等于斐波那契数列的数值*比例尺。如果真是这样,那就只有用矩形的四个顶点坐标来绘制矩形了。
为了验证我的想法,我写了一段代码。绘制一个直径为300像素的圆形,然后从圆弧上的一点向圆心画一条直径,代码如下:
效果如下:
很显然,我的想法错了。直径两端都落在圆弧上,通过三角函数绘制的圆形其精度还是很高的。
所以最后我又继续检查处理91°的代码,最终把问题解决了。
6.2 中断和单步运行
很遗憾,scratch没有中断和单步运行的功能。但是我们可以想办法控制程序的运行节奏和方向。
比如在程序中添加
指令,只要程序走到这里,就可以显式的提示我们。
或者设置一些参数,并让他显示在舞台上,就可以判断程序的运行状态。
在程序运行期间,可以直接修改程序代码,能够马上看到运行效果,且不需要重新点击小绿旗。
6.3 注释
编写代码的过程中,多写一些注释是个好习惯。最差的情况下就是给自己指明了方向。
7 结束语
我入门不久,能力有限,希望大家多多包涵。这个程序并不复杂,一路跌跌撞撞。发个帖子目的只是为了多交朋友,探讨学习心得。
感谢郎郎的建议,让我写了这个心得,这又是一次加深记忆的过程。多写多练才是王道。对于刚入门的朋友来说,调试程序比写程序更能提高编程能力。
感谢李泽老师,以后会多看看你的作品,学习技能和思想。
祝大家玩好scratch。