2018-05-16

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。

推荐阅读更多精彩内容

  • 在我们之前做项目的时候,肯定是用到过很多控件的,那些都是系统或者别人设计出来的。看到那些炫酷美观的界面,你有没有想...
    季白zy阅读 145评论 0 1
  • HTTP简介 HTTP协议是Hyper Text Transfer Protocol(超文本传输协议)的缩写,是用...
    一个_快乐的人阅读 95评论 0 0
  • git检查与放弃本地的代码修改情况 git diff 可以查看当前没有add 的内容修改(不在缓冲区的文件变化) ...
    亚哈吧阅读 30评论 0 0
  • 你不肯上课,我就说:奖励你六面红旗吧,实际上这个数目是目前为止一次性奖励的最大数目了。 我于是开始思考以下几个问题...
    豆蔻妈阅读 136评论 0 0
  • 这几天一直在想着打电话沟通下湉湉的事情。打电话吧不知道你是否方便,微信吧感觉每一次都没有反应,前天打电话一直没有接...
    湉宝贝儿阅读 31评论 0 0