超越模仿、锐意创新
前几天,王爱胜老师在“蓝调百香果”微信公众号推送了一篇文章《深度趣味编程|14.腊梅傲雪》,给出了魏振东老师编写的海龟绘制创意图形“腊梅傲雪”源代码。我认真阅读了代码,学到了很多东西,并在原有代码的基础上进行了整理和改进,给出了一段更为紧凑和通用的代码,受到了王老师的称赞,心里十分高兴。
高兴之余,我突然想到,自己对源代码进行分析和再创作的过程,就是一个学习提高的过程,如果能够把这个过程记录下来,形成示例,对今后的教学一定也会很有帮助。阅读源代码是学习编程的一种重要方法,我们要从“高手”们创作的代码中学习代码编写方法、算法原理和设计思路,从而提高自己的编程水平。
在教学过程中,我们不仅要引导学生学习经典的“完美”代码,也要让他们阅读原生态、 “有瑕疵”的代码,体会代码的优点和创新之处,分析其不足,并尝试写出更好的代码,让学生理解原创的不易和精益求精的必要性。
今天,我就抛砖引玉,把自己学习魏振东老师 “腊梅傲雪”源代码的过程记录下来,请各位老师多多批评指正。
剖析《海龟绘图“腊梅傲雪”》的创作过程
魏振东老师的作品逻辑清晰、浅显易懂、创意十足,尤其是梅花和梅枝的绘制简洁明了,画面具有中国水墨画特征,韵味十足。
魏老师把画雪花和画腊梅等操作设置为专门的功能模块,还把最常用的移动画笔操作抽象成自定义函数,体现了模块化编程的思想。我特别喜欢魏老师设置的gotopos(x, y)函数,并在后面的改进代码中多次用到它。
认真分析了源代码以后,在感叹魏老师精彩创意的同时,我也发现了原版代码的一些不足,并产生了一些新的想法,决心来一个二次创作。
原版代码的第一个不足是题字和写诗部分的代码不够紧凑,代码重复度较高,而且不便于扩展到其他诗词。
为了使代码更紧凑和通用,我设置了2个变量来存储标题和作者,设置了一个列表来存储诗词内容,每个列表元素表示一句诗;再设置好标题、作者和诗词的字体大小和书写位置;然后使用一重循环来输出标题和作者,二重循环输出诗词内容。这样仅用13行就实现了原来21行才能实现的功能,而且可以方便地扩展到其他诗词。
源代码中最重要也最难理解的部分是关于生成梅花路径规则的代码。
本作品的梅花和梅枝图案本质上是一个分形图形,分形通常使用递归算法来绘制,也可以用栈数据结构来存储表示路径的字符串,模拟递归过程。魏老师采用的是后者,他先使用一个字典rules来存储生成梅花路径的规则——令人疑惑之处在于源代码中有F和X两种规则,但程序实际只使用了F规则——所以我在改进的代码中只保留了F规则。
然后再使用一个for循环,通过apply_rules函数对路径进行迭代扩展。扩展的方法为,遇到F或X字符就将其扩展成rules[‘F’]或rules[‘X’],否则原样拼接。如此循环4次(相当于递归深度为4),即可获得最终的生成梅花路径。
最后根据迭代生成的梅花路径,调用自定义函数draw_path(path),按照规则绘制出腊梅图形。
仔细分析了apply_rules(path, rules)函数之后,我发现原版代码不够简明。在去掉不必要的X字符后,遍历字符串path,根据生成梅花路径规则,使用append()方法来生成新的路径,比原版效率要高一些。
虽然相对原版代码,改进版1更为简明,但是总感觉没有充分利用Python字典的灵活特性,难以令人满意。经过认真思考,我终于发现可以让字典变量rules发挥更大的作用,直接使用列表生成式构建列表,再转化成字符串path,这样就可以省略自定义函数apply_rules()了,代码得到进一步简化,有点Pythonic代码的味道了。
自定义函数draw_path(path)是本作品的核心代码,使用多分支语句,根据规则绘制腊梅图形。应该说这段代码的逻辑还是比较清晰的,结合梅花路径规则,可以帮助我们理解腊梅图形的绘制方法。
为了更好地理解代码,我先自己在纸上画出递归深度为1层的腊梅图形,然后又逐次分析了递归深度为2和3的腊梅图形。反复琢磨以后,终于明白了绘制腊梅图形的原理。没想到这简单图形的叠加组合,竟能呈现出如此美丽的图案。再一次为魏老师的精彩创意点赞!
经过比较分析,我发现原版代码还有一些多余的地方,于是对其进行了简化。
到这里为止,我都只是对原版代码做了一些表面上的优化,使其看起来更简明易懂,并没有实质上的改进。能不能从功能或者结构上做进一步改进呢?
我想到了两个改进点:第一个改进点是为梅花图形填充颜色,第二个改进点是把原来的迭代算法替换成更简明的递归算法。
首先是为梅花图形填充颜色。这个看起来简单,做起来可不容易。为了让梅枝能够向右侧上下分叉伸展,魏老师在绘制梅花图形时,并不是连续行笔绘制菱形,而是上下两个部分分开来绘制的,这样不能直接得到一个封闭图形,因此不能直接填充颜色。
为了帮梅花图形填充颜色,需要先区分菱形梅花节点和菱形梅花路径,再单独绘制菱形梅花并填充颜色。
那么如何从复杂的路径中找到梅花节点呢?
我注意到基本单元的路径规则是'aFFb[-F++F][+F--F]++F--F',其中生成梅花节点的部分为'[-F++F][+F--F] '。即当’[‘和’]’之间的字符个数为5时,说明这段长度为14的子串刚好表示一个梅花节点。
我们可以单独处理梅花节点,连续行笔绘制一个封闭的菱形,再填充颜色——为了让画面更生动,我让每朵花都有2/5的概率变成红色。因为增加了判断并单独处理梅花节点的功能,代码变长了,但功能也变强了。
因为递归算法本来就是绘制分形的基本方法,所以把原来的迭代算法替换成递归算法难度不大,只要根据构造梅花路径的规则,一步步编写代码就行了。为了简化代码,我设置了一个辅助函数recursion()来间接递归调用函数draw_flower(),算是本文的亮点之一。
当然,也可以对递归函数draw_flower()做一些修改,使其实现为梅花节点填充颜色的功能,原理和迭代算法是一样的——略有不同之处在于单独处理梅花节点部分的语句块。因为迭代算法记录了所有的路径,只需根据路径规则按顺序绘图即可,故绘制梅花节点时不需要绘制多出的分叉枝条;但是递归算法没有记录绘制图像的路径,而是在到达递归出口时才绘制图形,所以需要把基本单元图形绘制完整(包括菱形梅花和分叉枝条),如果漏掉分叉枝条,则无法正确绘制后续图形。
好了,到这里,我就把原版代码的闪光点和不足之处都做了分析,还把自己的思考过程和改进方法都完整地呈现给了大家。由于个人水平有限,分析过程难免有失误,所谓的“改进代码”也不一定就比原版代码好,各位老师也肯定能够给出更优雅的代码。希望我抛出的土砖能引来更多的美玉,欢迎大家批评指正,多提宝贵意见。
需要本文word版和源代码的,可以加入“Python算法之旅”知识星球参与讨论和下载文件,“Python算法之旅”知识星球汇集了数量众多的同好,更多有趣的话题在这里讨论,更多有用的资料在这里分享。
我们专注Python算法,感兴趣就一起来!
相关优秀文章: