今回はちょっと不思議なプログラムを作ってみます。 以下の短いものを実行してみます。
def factorial(i): if i>1: return i * factorial(i-1) else: return i N = 3 print(factorial(N))
これはNの階乗を求めるプログラムです。N=3なので3x2x1=6という結果を出します。N=4なら4x3x2x1=24になります。
仕組みを説明します。 1行目で関数factorialを定義して、iを引数で受け取ります。 2行目で「if」で、iが1より大きいならば、「i * factorial(i-1)」の計算結果を返します。 4行目で「if」でiが1より小さい場合は、iの値を返します。
これでなぜ正しく計算できるのでしょう。 N=3なら、i * factorial(i-1)は、3factorial(3-1)=3factorial(2)となりますね? factorial(2)で関数の処理しますから、2factorial(2-1)=2factorial(1)です。 factorial(1)でまた関数の処理をしますが、「else:」に該当するので1を返します。 つまり、factorial(2)=2、factorial(1)=1です。 結果3x2x1=6となります。
ちょっと解りにくいのですが、これを「再帰(ネスト)」と呼びます。 関数が自分の中でグルグル回っている、という感じです。 これは同じ計算を繰り返す場合、複雑になるのを防ぐことができます。 「フラクタル図形」というのを聞いたり見たことがあると思いますが、それが再帰です。
今回は3dsmaxでそれをやってみたいと思います。
import MaxPlus as mp def createTeapot(): obj = mp.Factory.CreateGeomObject(mp.ClassIds.Teapot) obj.ParameterBlock.Radius.Value = 5.0 return mp.Factory.CreateNode(obj) def treeOfSpheres(parent, width, xinc, depth, maxdepth): if depth == maxdepth: return for i in range(width): n = createTeapot() n.Parent = parent n.SetLocalPosition(mp.Point3(0, i * xinc, 15)) treeOfSpheres(n, width, xinc * width, depth + 1, maxdepth) mp.FileManager.Reset(True) treeOfSpheres(createTeapot(), 3, 10, 0, 2)
これを実行すると、こんな感じです。
階層を見ると右列がNo.1,2,3です。この3つが最初にできます。
2段目が親になっています。
その下の階層に3段目が並んでいます。
プログラムでパーツの名前を設定ないので、生成した順に番号が付いています。 つまり、2段目の1つを作った後に3段目を3つ作ってるのを繰り返してます。
ちょっと良く解らないかもしれませんので、いろいろ実験してみましょう。 最後の行を変えて実行してみます。赤い文字が変更点です。
treeOfSpheres(createTeapot(), 3, 10, 0, 1)
2段で3個だけできました。2回だけ再帰したことになります。 また違う実験をしてみます。
treeOfSpheres(createTeapot(), 2, 10, 0, 2)
3段で2個づつ複製して再帰しています。最後にmaxdepthを増やしてみます。
treeOfSpheres(createTeapot(), 2, 10, 0, 4)
つまり、widthの値が1回分のコピー数でfor文で繰り返していて、depthとmaxdepthの差が高さの段数を決めています。 maxdepthはもっと大きくすると凄い数のティーポットが自動作成できます。
なぜこのような結果になるのでしょうか?不思議ですね・・・
では、説明していきます。1と16行目は毎度お馴染みなので省略します。 2~5行目もお馴染みのティーポットを作成して、大きさ5にするだけですので、これも省略です。
7行目で関数treeOfSpheresを定義しています。
treeOfSpheres(createTeapot(), 3, 10, 0, 2)
17行目で実行していて、この関数は引数が5つあります。 width,xinc,depth,maxdepth←,3, 10, 0, 2 と代入して関数を実行しています。 最初の引数のcreateTeapot()が特別で、これだけでTeapotが作成され、それがparentになり関数に行きます。これは原点に生成されます。
8行目でif depth == maxdepth:で2つの値を比較して同じなら何もしません。 今回は0と2なのでOKです。 10行目で、forでwidth=3なので3回繰り返します。 n = createTeapot()でティーポットを作成する関数をnに入れます。この命令だけでティーポットが生成されます。
n.Parent = parentで親子関係を作ります。 引数で、parent=createTeapot()なので、ティーポットを作成する度に最初のティーポットの子供になります。 n.SetLocalPosition(mp.Point3(0, i * xinc, 15)) これは前々回にやった、移動の命令を1行で設定してます。 X=0、Z=15で、Yはforでi * xincづつ増加します。今回は10です。ティポットが半径5なのでピッタリ並びます。
14行がtreeOfSpheres関数があり再帰して繰り返し処理をします。 treeOfSpheres(n, width, xinc * width, depth + 1, maxdepth) これでn=createTeapot()、widthはそのまま、Y方向は width倍で、depthは1増加で、maxdepthがそのままです。
つまり、2段目の3つのティーポットが一番下のティーポットの立場になり、子供を3つを3段目にそれぞれ生成します。よって、1→3→9個となります。 depthが1づつ増えるので、0→1→2で9行目のReturenで終了します。
再帰はちゃんとやめる仕組みを作らないと無限に終わらない、という問題を起こしますので注意が必要です。