|
2.6.9 ループと制御構造体
Mathematicaプログラムの実行とは,一連のMathematica式の評価を意味する.単純なプログラムでは,式はセミコロンで区切られ,1つずつ評価されることが多い.しかし,多くの場合は,式を繰り返し評価する必要があり,ある種の「ループ」を使う必要が出てくる.

簡単なループ構成体
iを1から4にふったときのPrint[i^2]を評価する.
In[1]:= Do[Print[i^2], {i, 4}]




ループを使い,kを刻み幅2で2から6に ふったときのtに対する割当てを繰り返し行う.
In[2]:= t = x; Do[t = 1/(1 + k t), {k, 2, 6, 2}]; t
Out[2]= 
関数Doで使われる反復条件はTableやSum等の関数と同様に指定する.後者の関数でできたように,一連の反復指定を与えることで,Do式でも複数レベルでネストされたループを構成することができる.
ループを作り,iを1から4までふり,各iでjを1からi-1までふる.
In[3]:= Do[Print[{i,j}], {i, 4}, {j, i-1}]






場合によっては,反復変数の値をふらずに,単に特定の操作を繰り返したい.Tableや他の反復関数を使ってもこの種の反復操作は行えるが,Do式でも行うことができる.
t = 1/(1+t)による割当てを3回繰り返す.
In[4]:= t = x; Do[t = 1/(1+t), {3}]; t
Out[4]= 
Do式の内部に手続きを入れることもできる.
In[5]:= t = 67; Do[Print[t]; t = Floor[t/2], {3}]




関数の繰返し適用
Doを使えば,反復変数をいろいろな値にふり特定の式を評価することで一連の操作を繰り返すことが可能である.それでも,2.2.2に示したような関数型プログラミングの構成体を使うと,分かりやすく,また,効率的なプログラムを記述することができる.例えば,関数Nest[f, x, n]を使えば関数を繰り返し,再帰的に式に適用する,ということが可能になる.
fを3回ネストさせる.
In[6]:= Nest[f, x, 3]
Out[6]= 
純関数をネストさせ,前述のDoの例で 作った式と同じ計算をする式を作る.
In[7]:= Nest[ Function[t, 1/(1+t)], x, 3 ]
Out[7]= 
このように,Nestは,関数を指定した回数だけ繰り返し適用するのに使う.それでは,関数を繰り返し適用させ,結果が変化しなくなったら自動的に停止させる,という処理を行いたい場合はどうするのか.そのときは,関数FixedPoint[f, x]を使い定常点の探索を行うようにすればよい.
FixedPointは,結果が変わらなくなるまで関数を繰り返し適用する.
In[8]:= FixedPoint[Function[t, Print[t]; Floor[t/2]], 67]








Out[8]= 
関数FixedPointを使うことで,カーネルの評価プロセスそのものや,//.記号を使った操作をまねることができる.FixedPointでは,2回の逐次結果に変化がなくなるまで関数の適用が続けられる.一方NestWhileでは,任意の関数がTrueでなくなるまで続けられる.

評価処理の非局所的な制御
Do式にCatchを作用させる.評価中にThrowが現れるとCatchの処理は停止され,そのときのiの値が返される.
In[9]:= Catch[Do[Print[i]; If[i > 3, Throw[i]], {i, 10}]]




Out[9]= 
ThrowとCatchを組み合せて使うと,Mathematicaの評価プロセスを監視することができるため便利である.評価を監視したい式には,Catchを作用させておく.評価処理中にThrowが現れると,その時点で進行中の処理は停止し,Throwに与えられている引数の現行値が返される.
Scanを作用させ,Print関数をリストの各値に逐次に適用する.Scan処理終了後,空文字(Null)が返される.
In[10]:= Scan[Print, {7, 6, 5, 4}]




Throwが現れる時点で,Scan処理は停止する.Catchの戻り値として,Throwの引数の持つ現行値が返される.
In[11]:= Catch[Scan[(Print[#]; If[# < 6, Throw[#]])&, {7, 6, 5, 4}]]



Out[11]= 
Mapを使っても同じ結果を得ることができる.ただし,Mapだと,評価中にThrowが現れないときはリストが返されることになる.
In[12]:= Catch[Map[(Print[#]; If[# < 6, Throw[#]])&, {7, 6, 5, 4}]]



Out[12]= 
ThrowとCatchを使えば,関数的なプログラミング構成体における操作処理を特定条件が満たされるまで続行させ,満たされなった時点で中断させることができる.Throwで評価を中断すると,得られる結果は途中結果である.途中結果なので,式は,仮に最後まで評価を続けたときの式とは構成上多少違ってくることに注意してほしい.
関数を繰り返し適用し,リストを生成する.
In[13]:= NestList[1/(# + 1)&, -2.5, 6]
Out[13]= 
Throwがないため,上の式と同じ結果が得られる.
In[14]:= Catch[ NestList[1/(# + 1)&, -2.5, 6] ]
Out[14]= 
今度は,NestListの評価が中断され,1つあるThrowの引数が返される.
In[15]:= Catch[ NestList [If[# > 1, Throw[#], 1/(# + 1)]&, -2.5, 6] ]
Out[15]= 
ThrowとCatchはともに大域的な働き方をする.Throwがどこに,またどのように配されても,一度それが現れると,評価処理は必ず中断され,制御は直接Catchに戻される.
Throwによりfの評価が中断され,Catchの戻り値としてThrowの引数であるaが出力される.fは無視される.
In[16]:= Catch[ f[ Throw[ a ] ] ]
Out[16]= 
関数を定義する.この関数は,引数に10より大きい値を入力した場合,Throwを発生させる.
In[17]:= g[x_] := If[x > 10, Throw[overflow], x!]
Throwは起らない.
In[18]:= Catch[ g[4] ]
Out[18]= 
この場合,gの評価中に起るThrowにより,制御はCatchに戻される.
In[19]:= Catch[ g[40] ]
Out[19]= 
短いプログラムでThrowとCatchを使うなら,Throw[value]とCatch[expr]の最も単純な書式を使うだけでよい.しかし,構成部分が増えプログラムが長くなると,Throw[value, tag]とCatch[expr, form]の書式を使った方がよいだろう.タグtagと形formを局所的な引数としておくことで,ThrowとCatchがプログラムの特定部分だけで機能するようにすることができる.
タグがあるため,Throwにより投げ出される値は内側のCatchでキャッチされる.
In[20]:= Catch[ f [ Catch[ Throw[x, a], a ] ], b ]
Out[20]= 
今度は,外側のCatchによりキャッチされる.
In[21]:= Catch[ f [ Catch[ Throw[x, b], a ] ], b ]
Out[21]= 
パターンでCatchの認識するタグを指定してもよい.
In[22]:= Catch[ Throw[x, a], a | b ]
Out[22]= 
こうすればタグaを完全に局所的なものとして維持できる.
In[23]:= Module[{a}, Catch[ Throw[x, a], a] ]
Out[23]= 
ここでの注意点は,Throwで使うタグは定数である必要はない.通常,それはどんな式であっても構わない.
Throwのタグの値が4より小さければ内側のCatchがキャッチし,Do式の処理が続行する.4以上になったら,外側のCatch で止められる.
In[24]:= Catch[ Do[ Catch[ Throw[i^2, i], n_ /; n < 4], {i, 10} ], _]
Out[24]= 
Throw[value, tag]とCatch[expr, form]の書式を使った場合,Catchの返してくる値はThrowに指定した第1引数valueになる.また,Catchの書式をCatch[expr, form, f]にすると ,単なるvalueではなく,任意関数fを作用させた式f[value, tag]が返される.
fがThrowの値xとタグaに適用される.
In[25]:= Catch[ Throw[ x, a ], a, f ]
Out[25]= 
Throwがなければ,fは決して使われ ない.
In[26]:= Catch[ x, a, f ]
Out[26]= 

一般的なループ構成体
構造化アプローチを取るなら,Do,Nest,FixedPoint等の関数を使いループを構築するとよい.また,ThrowとCatchを併せて使えば,ループを監視,制御することも可能である.一方,構造にとらわれないループを作りたい場合は,関数WhileやForを使うとよい.任意条件下で繰返し処理を停止できるループを構築することができる.
Whileループは,条件が満たされなくなるまで実行される.
In[27]:= n = 17; While[(n = Floor[n/2]) != 0, Print[n]]




関数WhileとForは,C言語のwhileとforに構文上似ている.ただし,違いもある.例えば,コンマとセミコロンの使い方はC言語と逆である.
Forループの簡単な例を見てみる.i++はiの値を1増加させる.
In[28]:= For[i=1, i < 4, i++, Print[i]]



少し複雑なループを作ってみよう.実行させると,ループは,条件式i^2 < 10が満たされなくなった時点で終了する.
In[29]:= For[i=1; t=x, i^2 < 10, i++, t = t^2 + i; Print[t]]



WhileとForのループにおいて,まず評価されるのは条件式であり,その次に,本体の式が評価される.つまり,条件が満たされなければその時点でループは終了してしまう.このため,本体の式が評価されるのは条件式が真になるときだけである.
ループの条件式にはもとからFalse が与えられているので,本体の式Print[x]は完全に無視される.
In[30]:= While[False, Print[x]]
WhileやForによるループ,または,その他の手続きにおいて,式の評価は特定の手順に従って行われる.この評価手順をMathematicaプログラムの実行における制御フローと呼ぶ.
一般に,制御フローはなるべく単純なものがよい.制御フローが,プログラム実行中に生成される特定値に依存すればするほど,プログラムの構造や動作が分かりづらいものになってしまう.
WhileやForのループ体を使うより,関数的な構成体を使った方が,制御フローをより単純なものにできる.WhileやForのループでは,制御フローがプログラム実行時の条件式の値に左右されてしまうので,ループが複雑な構成になりがちである.とはいえ,ループであっても,制御フローは,普通,その本体で与えられた式の値に依存するようなことはない.
それでも,場合によっては,本体の式の値に依存した形でフローを変えてやらなければならない.これを行う方法として,まず,関数的なプログラミングの考え方に適合した手法,つまり,関数ThrowとCatchを使う方法が考えられる.さらに,MathematicaにはC言語で提供される制御フローの操作用関数に相当した各種の関数が用意されているので,それらを利用するのも便利である.

フローの制御関数
tが19を超え,制御がBreak[ ]に達した時点でループが停止する.
In[31]:= t = 1; Do[t *= k; Print[t]; If[t > 19, Break[]], {k, 10}]




k < 3のときは,Continue[ ]に制御が行き,t += 2の計算は実行されずにループが続行される.
In[32]:= t = 1; Do[t *= k; Print[t]; If[k < 3, Continue[]]; t += 2, {k, 10}]










Return[expr]を使い,戻り値exprを指定することも可能である.ネストされた関数式から一挙に退避できることから,Throwを非局所的なリターン機能と考えることもできる.エラー処理に利用すると便利である.
Returnを使った例を見てみる.必ずしもReturnを使わなくてもこの手続きを記述することができる.
In[33]:= f[x_] := (If[x > 5, Return[big]]; t = x^3; Return[t - 7])
引数が5より大きいので,手続きでは最初のReturnが使われる.
In[34]:= f[10]
Out[34]= 
引数が負の数のときはエラー値errorを返すように関数を定義する.
In[35]:= h[x_] := If[x < 0, Throw[error], Sqrt[x]]
実行してもThrowに制御は移らない.
In[36]:= Catch[ h[6] + 2 ]
Out[36]= 
今度は,負になるからThrowが生成され,Catchの戻り値はerrorになる.
In[37]:= Catch[ h[-6] + 2 ]
Out[37]= 
Continue[ ]やBreak[ ]の関数はループの始点もしくは終点に制御を移すために使う.場合によっては,ループの外にある手続き内の任意な位置に制御を移したい.そのようなときは,転送先を関数Labelであらかじめラベル指定をしてから関数Gotoを使い実際に制御を移す.
LabelとGotoを使ったループを構成する.qが6を超えるまでループが続行 する.
In[38]:= (q = 2; Label[begin]; Print[q]; q += 3; If[q < 6, Goto[begin]])


注意してほしいが,Gotoを使うには同じ手続き内にLabel指定がなければならない.ただし,Goto 指定を使うとフロー構造が入り組んだものになり,プログラムがどう機能しているのか分かりづらくなるので使用には注意が必要である.
|