Goal: 藉由有趣的「海龜繪圖」學會基礎的 Python 程式設計
本篇介紹以 Python 海龜繪圖實作 1. 二叉樹碎形 2. Koch 雪花碎形, 最後討論遞迴 recursive 與 疊代 iterated (迴圈 for loop) 是否可以互相轉換.
“Talk is cheap. Show me the code.”
― Linus Torvalds
老子第41章
上德若谷
大白若辱
大方無隅
大器晚成
大音希聲
大象無形
道隱無名
拳打千遍, 身法自然
“There’s no shortage of remarkable ideas, what’s missing is the will to execute them.” – Seth Godin
「很棒的點子永遠不會匱乏,然而缺少的是執行點子的意志力。」—賽斯.高汀
本系列文章之連結
-
從turtle海龜動畫學習Python-高中彈性課程1 link
-
從turtle海龜動畫 學習 Python - 高中彈性課程系列 2 安裝 Python, 線上執行 Python link
-
從turtle海龜動畫 學習 Python - 高中彈性課程系列 3 烏龜繪圖 所需之Python基礎 link
-
從turtle海龜動畫 學習 Python - 高中彈性課程系列 4 烏龜開始畫圖 link
-
從turtle海龜動畫 學習 Python - 高中彈性課程系列 5 用函數封裝重複性指令-呼叫函數令烏龜畫正 n 邊形 link
-
從turtle海龜動畫 學習 Python - 高中彈性課程系列 6 畫多重旋轉圓,螺旋正方形 link
-
從turtle海龜動畫 學習 Python - 7 遞歸 recursive - 高中彈性課程系列 link
-
從turtle海龜動畫 學習 Python - 高中彈性課程系列 8 碎形 (分形 fractal) link
-
從turtle海龜動畫 學習 Python - 高中彈性課程系列 8.1 碎形 L-system link
-
從turtle海龜動畫 學習 Python - 高中彈性課程系列 9 Python 物件導向介紹 link
-
從turtle海龜動畫 學習 Python - 高中彈性課程系列 9.1 Python 物件導向的練習 link
-
從turtle海龜動畫 學習 Python - 高中彈性課程系列 10 藝術畫 自定義海龜形狀 link
-
從turtle海龜動畫 學習 Python - 高中彈性課程系列 10.1 藝術畫 python繪製天然雪花結晶 https://blog.csdn.net/m0_47985483/article/details/122262036 link
-
從turtle海龜動畫 學習 Python - 高中彈性課程系列 10.2 藝術畫 Python 製作生成式藝術 link
-
從turtle海龜動畫 學習 Python - 高中彈性課程系列 11.1 氣泡排序 - 用 turtle 呈現演算法之執行動作 link
-
從turtle海龜動畫 學習 Python - 高中彈性課程系列 11.2 maze 迷宮 - 用 turtle 呈現演算法之執行動作 link
-
從turtle海龜動畫 學習 Python - 高中彈性課程系列 11.3 連分數演算法與轉轉相除法- 用 turtle 呈現演算法之執行動作 link
-
從turtle海龜動畫 學習 Python - 高中彈性課程系列 11.4 最短路徑 Dijkstra- 用 turtle 呈現演算法之執行動作 link
文章目录
(原 part2 之碎形部分)
7 Fractal tree 碎形樹- recursive 之應用
觀摩 YouTube上講解以 JavaScript 語言模擬"碎形樹" 的影片
Ref: https://www.youtube.com/watch?v=0jjeOYMjmDU
link
Ex: 你可以用 Python 的 turtle module 重現一次
“The Coding Train” 系列中, 模擬"碎形樹" 的動畫嗎?
Python turtle 的 demo 也有:
較單純沒有隨機的樹
本篇作者模仿 “The Coding Train” 的用色呈現,
tree1_angle_headingLocal_20201011.py
背景色設為黑色
turtle.bgcolor('black')
T.pencolor('white')
codes 可以看下面 7.2
tree1_angle_headingLocal_20201011.py
把背景色修改一下
7.1 最簡單 2 叉碎形樹, 樹枝夾角為 45 度
因為demo 的源碼較不好讀,
以下我們試著用自己的較單純的設計方法去實現簡單2叉碎形樹的繪製,
以下是粗略的結構
# By Prof. P-J Lai MATH NKNU 20201007
# 以下是沒有隨機的版本
import turtle
T = Turtle()
def tree1(pos, length, angle, r):
if length < 0.5:
return
T.goto(pos)
T.lt(angle)
T.fd(length)
tree1(pos?, length*r, angle=angle-45)
tree1(pos?, length*r, angle=angle+45)
return
以下補上codes 細節
如果自己試著寫, 會發現相當困難, 牽涉到遞迴的執行, 是層層往深處呼叫, 直到最底層, 才獲得答案, 才一層一層往回給出前一層之解答,
所以必須先記錄往下疊代前, 當下的位置與角度 ( 此處如果你沒有自己試著寫, 並試著跑第一層第二層看看執行結果, 找出錯誤的原因, 嘗試錯誤, 你會不知道這段話的意義!)
# By Prof. P-J Lai MATH NKNU 20201007
# 修改自河西朝雄的 C 程式碼
# 以下是沒有隨機的版本
import turtle
T = turtle.Turtle()
r = 0.7
#T.lt(90)
def tree1(pos, length, angle):
if length < 10:
return
T.setheading(angle)
T.fd(length)
pos = T.position()
angle1=angle-45
tree1(pos, length*r, angle1)
angle2=angle+45
T.penup()
T.goto(pos)
T.pendown()
tree1(pos, length*r, angle2)
return
tree1((0,0),100, 0)
執行結果
把顏色跟角度調整一下
程式碼改一下
import turtle
T = turtle.Turtle()
r = 0.7
#T.lt(90)
T.shape('turtle')
T.pencolor('red')
T.pensize(3)
T.fillcolor("blue")
,,,,
tree1((0,0),100, 90)
以下為往正北出發, 樹枝夾角=45
7.2 樹枝夾角為 可調整
以下我們將程式碼修改成增加夾角可以調整, 不一定都要是45度,
函數增加一個輸入引數
tree1(pos, length, headingAngle, angle)
結果發現錯誤, 原因在全域變數之問題
以下為往正北出發, 樹枝夾角=30, codes有 bug 之圖
以下為錯誤之版本, 沒有增加局部變數 headingAngle1, headingAngle2
tree1_angle_error_csdn_2_20201011.py
# By Prof. P-J Lai MATH NKNU 20201007
# 修改自河西朝雄的 C 程式碼
# 增加夾角的引數
# 以下是以下為錯誤之版本, 沒有增加局部變數 headingAngle1, headingAngle2,
# 兩個 分枝使用同一個局部變數 headingAngle
# tree1_angle_error_csdn_2_20201011.py
import turtle
T = turtle.Turtle()
r = 0.7
#T.lt(90)
T.shape('turtle')
T.pencolor('red')
T.pensize(3)
T.fillcolor("blue")
def tree1(pos, length, headingAngle, angle):
if length < 10:
return
T.setheading(headingAngle)
T.fd(length)
pos = T.position()
#headingAngle=headingAngle-45
headingAngle=headingAngle-angle
tree1(pos, length*r, headingAngle, angle)
headingAngle= headingAngle + angle
#headingAngle2= headingAngle + angle
T.penup()
T.goto(pos)
T.pendown()
tree1(pos, length*r, headingAngle, angle)
return
#tree1((0,0),100, 0)
tree1((0,0),100, 90, 30)
修正後:
以下是修訂之後的版本, 增加2個局部變數 headingAngle1, headingAngle2, 會紀錄當下的 heading angle (海龜的前進方向)
以下為往正北出發, 樹枝夾角=30, codes 正確 之圖
tree1_angle_headingLocal_20201011.py
# By Prof. P-J Lai MATH NKNU 20201007
# 修改自河西朝雄的 C 程式碼
# 增加夾角的引數
# 以下是沒有隨機的版本
tree1_angle_headingLocal_csdn_20201011.py
import turtle
T = turtle.Turtle()
r = 0.7
#T.lt(90)
T.shape('turtle')
T.pencolor('red')
T.pensize(3)
T.fillcolor("blue")
def tree1(pos, length, headingAngle, angle):
if length < 10:
return
headingLocal = headingAngle
T.setheading(headingLocal)
T.fd(length)
pos = T.position()
#headingAngle=headingAngle-45
headingLocal_1=headingLocal - angle
tree1(pos, length*r, headingLocal_1, angle)
headingLocal_2= headingLocal + angle
T.penup()
T.goto(pos)
T.pendown()
tree1(pos, length*r, headingLocal_2, angle)
return
#tree1((0,0),100, 0)
tree1((0,0),100, 90, 30)
7.3 樹枝夾角改為隨機擾動
樹枝夾角增加隨機擾動, 擾動值範圍 = 夾角的 0 ~ 0.7倍
codes 增加:
if random.randint(0,1)>0:
randomPerturbation = random.random()*0.7
else:
randomPerturbation = -random.random()*0.7
headingLocal_1=headingLocal - angle*(1+randomPerturbation)
tree1_random_angle(pos, length*r, headingLocal_1, angle)
tree1_random_angle_20201011.py
# By Prof. P-J Lai MATH NKNU 20201007
# 修改自河西朝雄的 C 程式碼
# 增加夾角的引數
# 以下是樹枝夾角改為隨機擾動的版本
import turtle
import random
T = turtle.Turtle()
r = 0.7
#T.lt(90)
T.shape('turtle')
T.pencolor('red')
T.pensize(3)
T.fillcolor("blue")
def tree1_random_angle(pos, length, headingAngle, angle):
if length < 10:
return
headingLocal = headingAngle
T.penup()
T.goto(pos)
T.pendown()
T.setheading(headingLocal)
T.fd(length)
pos = T.position()
#headingAngle=headingAngle-45
if random.randint(0,1)>0:
randomPerturbation = random.random()*0.7
else:
randomPerturbation = -random.random()*0.7
headingLocal_1=headingLocal - angle*(1+randomPerturbation)
tree1_random_angle(pos, length*r, headingLocal_1, angle)
if random.randint(0,1)>0:
randomPerturbation = random.random()*0.7
else:
randomPerturbation = -random.random()*0.7
headingLocal_2= headingLocal + angle*(1+randomPerturbation)
T.penup()
T.goto(pos)
T.pendown()
tree1_random_angle(pos, length*r, headingLocal_2, angle)
return
#tree1((0,0),100, 0)
tree1_random_angle((0,0),100, 90, 45)
以下是 長 200, 夾角 45 隨機小擾動, 起始位置 = (0, -300)
7.4 左右分枝不同的收縮率
以下參考河西朝雄的 ch8, 讓左右是不同的收縮率, 左邊的收縮率是右邊的 0.8 倍, 造成枝葉偏右的型態
>>>tree1_random_angle_diff_length((0,-300),150, 90, 20)
tree1_random_angle_diff_length(pos, length, headingAngle, angle)
# By Prof. P-J Lai MATH NKNU 20201007
# 修改自河西朝雄的 C 程式碼
# 增加夾角的引數
# 以下是樹枝夾角改為隨機擾動的版本
# 以下參考河西朝雄的 ch8, 讓左右不同的縮收率, 左邊是右邊的0.8倍, 造成枝葉偏# 右的型態
import turtle
import random
T = turtle.Turtle()
right_ratio = 0.8
left_ratio = 0.64
#T.lt(90)
T.shape('turtle')
T.pencolor('red')
T.pensize(2)
T.fillcolor("blue")
T.speed(0)
turtle.tracer(0,0)
def tree1_random_angle_diff_length(pos, length, headingAngle, angle):
if length < 10:
return
headingLocal = headingAngle
T.penup()
T.goto(pos)
T.pendown()
T.setheading(headingLocal)
T.fd(length)
pos = T.position()
#headingAngle=headingAngle-45
if random.randint(0,1)>0:
randomPerturbation = random.random()*0.7
else:
randomPerturbation = -random.random()*0.7
headingLocal_1=headingLocal - angle*(1+randomPerturbation)
tree1_random_angle_diff_length(pos, length*right_ratio, headingLocal_1, angle)
if random.randint(0,1)>0:
randomPerturbation = random.random()*0.7
else:
randomPerturbation = -random.random()*0.7
headingLocal_2= headingLocal + angle*(1+randomPerturbation)
T.penup()
T.goto(pos)
T.pendown()
tree1_random_angle_diff_length(pos, length*left_ratio, headingLocal_2, angle)
return
tree1_random_angle_diff_length((0,-300),150, 90, 20)
Ex 左右分枝的長度收縮率 隨機擾動
Ex: 之前的 codes, 左右分枝的長度收縮率是在程式裡面直接給定, 例如, right_ratio = 0.8, left_ratio = 0.64,
請同學改成, 左右分枝的長度收縮率有一個隨機擾動的範圍, 並觀察圖形是否更自然更有變化.
8 Von Koch Curve 寇赫雪花碎形曲線- recursive 之應用
修改河西朝雄 8-7 KochRei62.c
遞迴 第 0 層, 就是畫一條直線段
以下為遞迴 1 層, 此形狀英文叫 motif, (動機), 一般碎形(分形) 就是持續用 motif 替換每一個小線段, 畫出 碎形的前幾層, 理論上的碎形, 是重複做無窮次替換而得之圖形.
以下為遞迴 2 層
以下為遞迴 5 層
#2013/11/28 by Prof. P-J Lai MATH NKNU 修改河西朝雄8-7KochRei62之執行
from turtle import *
def koch(turtle, n,leng): # Koch 遞迴的程序
if (n==0):
turtle.fd(leng)
else:
koch(turtle,n-1,leng)
turtle.lt(60)
koch(turtle,n-1,leng)
turtle.lt(-120)
# self.rt(120 )
koch(turtle,n-1,leng)
turtle.lt(60)
koch(turtle,n-1,leng)
def main():
T=Turtle()
T.shape('turtle')
T.color('blue','green')
T.speed(0)
#以下三行只是希望將烏龜的起始位置移到(-200,0)的地方
#以利烏龜出現在視窗較適當的位置, 當length改變時, 或是層數n 改時,
#此起始位置還要自己再修改
T.penup()
T.goto(-600,-100)
T.pendown()
koch(T,5,5)
main()
以下是參考tdemo fractalcurves.py 物件導向,
與 河西朝雄 8-7 KochRei62.c, Dr62_1.c 十字繡之寫法
兩份的寫法,
kochSquare() 是用長城形狀, 參考 Dr62_1.c 十字繡之寫法
# revise from tdemo_fractalcurves.py
# 修改河西朝雄8-7KochRei62,
# kochSquare 是用長城形狀, 參考 Dr62_1.c 十字繡之寫法
# Koch curve 20160520
from turtle import *
from time import sleep, time
class Koch(Pen):
def koch(self, dist, depth, dir):
if depth < 1:
self.fd(dist)
return
self.koch(dist / 3, depth - 1, dir)
self.lt(60 * dir)
self.koch(dist / 3, depth - 1, dir)
self.rt(120 * dir)
self.koch(dist / 3, depth - 1, dir)
self.lt(60 * dir)
self.koch(dist / 3, depth - 1, dir)
def kochSquare(self,dist, depth, dir):
if depth<1:
self.fd(dist)
return
self.kochSquare(dist/3, depth-1,dir)
self.lt(90*dir)
self.kochSquare(dist/3,depth-1,dir)
self.rt(90*dir)
self.kochSquare(dist/3,depth-1,dir)
self.rt(90*dir)
self.kochSquare(dist/3,depth-1,dir)
self.lt(90*dir)
self.kochSquare(dist/3,depth-1,dir)
def main():
kochT=Koch()
kochT.speed(0)
kochT.pensize(4)
kochT.pu()
kochT.setpos(-300,50)
kochT.pd()
kochT.size=6
kochT.shape("turtle")
kochT.color("blue","red")
#Screen().delay(100)
#Screen().tracer(8,25)
kochT.koch(500,2,1)
kochT.color("yellow","red")
kochT.pu()
kochT.setpos(-300,-100)
kochT.pd()
kochT.kochSquare(500,3,1)
return
if __name__ == '__main__':
msg = main()
print(msg)
mainloop()
Ex: 1. 將以上改寫成, 每小段之長度有隨機擾動,
2. 將以上改寫成, 每小段之正三角形方向為隨機往外或往內,
9. 遞迴 recursive 與 疊代 iterated (迴圈 for loop) 是否可以互相轉換?
-
循環 (for, while loop) 效能很高, 但是遞迴 (recursive) 的程式碼結構簡潔一目了然 (假設你已經很懂遞迴的語法).
-
尾遞迴效能與循環接近
在 5. 那裏我們舉了 2 n 2^n 2n, 累加, 費氏數列, 它們 recursive 寫法與 loop 寫法是可以互相轉換, 但是對一般的狀況, 兩者是否確實等價的?
在網路上查詢, 理論上是可以的, 但是在 知乎的文章:
所有递归都可以改写成循环吗?
https://www.zhihu.com/question/29373492
link
有回答:
栈底不可预见的时候,递归是无法有效地化为循环的。If you maintain your own stack it would be correct. Otherwise recursion can do things loops can’t, like walk a tree.[http://c2.com/cgi/wiki?RecursionVsLoop] link
作者:高木端
链接:https://www.zhihu.com/question/20418254/answer/15081000 link
来源:知乎
較常見的是將遞迴改寫成尾遞迴 tail-recursive, 尾遞迴效能類似 loop, 有許多程式之編譯器 (C, JavaScript等) 會自動把遞迴的程式最佳化為尾遞迴.
- 以下可以等進階時再細看
可以參考Baidu百科, n ! n! n! 的尾遞迴寫法:
Ref: https://baike.baidu.com/item/%E5%B0%BE%E9%80%92%E5%BD%92#reference-[1]-1439396-wrap link
示例3-2:
/*facttail.c*/
#include"facttail.h"
/*facttail*/
int facttail(int n, int a)
{
/*Compute a factorialina tail - recursive manner.*/
if (n < 0)
return 0;
else if (n == 0)
return 1;
else if (n == 1)
return a;
else
return facttail(n - 1, n * a);
}
Ex: 將 5 那裏我們舉了 2 n 2^n 2n, 累加, 費氏數列, 用尾遞迴改寫, 並比較尾遞迴, 遞迴, for loop 三種寫法 的效能差異.
- 以上可以等進階時再細看
References
-
觀摩 YouTube上講解以 JavaScript 語言模擬"碎形樹" 的影片
Ref: https://www.youtube.com/watch?v=0jjeOYMjmDU
link -
生成式藝術和演算法創作07 L, https://zhuanlan.zhihu.com/p/50712698 link
-
Michael Hansmeyer:塑造不可思议, https://v.youku.com/v_show/id_XNDc4MDM2NzA4.html link
-
河西朝雄的 C 程式碼
-
所有递归都可以改写成循环吗?
https://www.zhihu.com/question/29373492
link -
從 Python 海龜繪圖初入門到使用遞迴(遞歸) 畫嵌套圖形, 碎形及進階迷宮演算法等之很好的文章: 数据结构与算法(Python版)——(3)通俗易懂的介绍递归(上), https://blog.csdn.net/weixin_45901519/article/details/109456646 link
-
栈底不可预见的时候,递归是无法有效地化为循环的。If you maintain your own stack it would be correct. Otherwise recursion can do things loops can’t, like walk a tree.[http://c2.com/cgi/wiki?RecursionVsLoop] link
-
高木端, 链接:https://www.zhihu.com/question/20418254/answer/15081000 link 来源:知乎
-
n ! n! n! 的尾遞迴寫法, https://baike.baidu.com/item/%E5%B0%BE%E9%80%92%E5%BD%92#reference-[1]-1439396-wrap link
-
Python——畫一棵漂亮的樱花樹, https://blog.csdn.net/weixin_43943977/article/details/102691392 link
非常漂亮的樹! -
呈現碎形樹 “Coding Challenge #14: Fractal Trees - Recursive”
https://www.youtube.com/watch?v=0jjeOYMjmDU&t=8s, link -
Generative art in Python: Fractal Trees
https://youtu.be/EICpm9rnPjE link -
Generative art in Python: Basic Tiling, https://youtu.be/Cm_SzDlQ2cM link