ループと制御構造体
Mathematica プログラムの実行とは,一連の
Mathematica 式の評価を意味する.単純なプログラムでは,式はセミコロンで区切られ,1つずつ評価されることが多い.しかし,多くの場合は,式を繰り返し評価する必要があり,ある種の「ループ」を使う必要が出てくる.
| Do[expr,{i,imax}] | 反復変数i を刻み幅1で1からimax に増加させ,式expr を繰り返し評価する |
| Do[expr,{i,imin,imax,di}] | 反復変数i を刻み幅di でimin からimax に増加させ,式expr を繰り返し評価する |
| Do[expr,{i,list}] | i がlist からの値を取ってexpr を評価する |
| Do[expr,{n}] | 式expr をn 回評価する |
簡単なループ構成体
iを 1から 4に変化させたときの Print[i^2]を評価する. |
kを 2から 6まで刻み幅 2で変化させるループを使い, tに対する割当てを行う.
| Out[2]= |  |
|
関数
Doで使われる反復条件は
Table や
Sum等の関数と同様に指定する.後者の関数でできたように,一連の反復指定を与えることで,
Do式でも複数レベルでネストされたループを構成することができる.
ループを作り, iを 1から 4まで振り,各 iで jを 1から i-1までふる. |
場合によっては,反復変数の値は変えずに,単に特定の操作を繰り返したい.
Tableや他の反復関数を使ってもこの種の反復操作は行えるが,
Do式でも行うことができる.
| Out[4]= |  |
|
| Nest[f,expr,n] | 関数f を式expr にn 回適用する |
| FixedPoint[f,expr] | 関数f を繰り返して式expr に適用し,結果の式が変化しなくなる時点で停止する |
| NestWhile[f,expr,test] | 結果の式にtest を適用したものがTrueを返さなくなるまで,式expr に関数f を繰り返して適用する |
関数の繰返し適用
Doを使えば,反復変数をいろいろな値に振り特定の式を評価することで一連の操作を繰り返すことが可能である.それでも,
「関数を反復的に適用する」に示したような関数型プログラミングの構成体を使うと,分かりやすく,また,効率的なプログラムを記述することができる.例えば,関数
Nest[f, x, n]を使えば関数を繰り返し,再帰的に式に適用する,ということが可能になる.
| Out[6]= |  |
|
純関数をネストさせ,前述の Doの例で作った式と同じ計算をする式を作る.
| Out[7]= |  |
|
このように,
Nestは,関数を指定した回数だけ繰り返し適用するのに使う.それでは,関数を繰り返し適用させ,結果が変化しなくなったら自動的に停止させる,という処理を行いたい場合はどうするのか.そのときは,関数
FixedPoint[f, x]を使い定常点の探索を行うようにすればよい.
| Out[8]= |  |
|
関数
FixedPointを使うことで,カーネルの評価プロセスそのものや,
expr//.rules 等の関数操作をまねることができる.
FixedPointでは,2回の逐次結果に変化がなくなるまで関数の適用が続けられる.一方
NestWhileでは,任意の関数が
Trueでなくなるまで続けられる.
| Catch[expr] | Throw[value]が現れるまで式 expr を評価し続け,現れた時点で値 value を返す |
| Catch[expr,form] | tag がform にマッチするThrow[value, tag]が現れるまで式expr を評価し続け,現れた時点で値を返す |
| Catch[expr,form,f] | 値value の代りにf[value, tag]を返す |
評価処理の非局所的な制御
| Out[9]= |  |
|
Throwと
Catchを組み合せて使うと,
Mathematica の評価プロセスを監視することができるため便利である.評価を監視したい式には,
Catchを作用させておく.評価処理中に
Throwが現れると,その時点で進行中の処理は停止し,
Throwに与えられている引数の現行値が返される.
| Out[11]= |  |
|
Mapを使っても同じ結果を得ることができる.ただし, Mapだと,評価中に Throwが現れないときはリストが返されることになる.
| Out[12]= |  |
|
Throwと
Catchを使えば,関数的なプログラミング構成体における操作処理を特定条件が満たされるまで続行させ,満たされなくなった時点で中断させることができる.
Throwで評価を中断すると,得られる結果は途中結果である.途中結果なので,式は,仮に最後まで評価を続けたときの式とは構成上多少違ってくることに注意してほしい.
| Out[13]= |  |
|
Throwがないため,上の式と同じ結果が得られる.
| Out[14]= |  |
|
| Out[15]= |  |
|
Throwと
Catchはともに大域的な働き方をする.
Throwどこに,またどのように配されても,一度それが現れると,評価処理は必ず中断され,制御は直接
Catchに戻される.
| Out[16]= |  |
|
関数を定義する.この関数は,引数に10より大きい値を入力した場合, Throwを発生させる. |
| Out[18]= |  |
|
| Out[19]= |  |
|
短いプログラムで
Throwと
Catchを使うなら,
Throw[value]と
Catch[expr]の最も単純な書式を使うだけでよい.しかし,構成部分が増えプログラムが長くなると,
Throw[value, tag]と
Catch[expr, form]の書式を使った方がよいだろう.タグ
tag と形
form を局所的なものとしておくことで,
Throwと
Catchがプログラムの特定部分だけで機能するようにすることができる.
| Out[20]= |  |
|
| Out[21]= |  |
|
パターンで Catchの認識するタグを指定してもよい.
| Out[22]= |  |
|
こうすればタグ aを完全に局所的なものとして維持できる.
| Out[23]= |  |
|
ここでの注意点は,
Throwで使うタグは定数である必要はない.通常,それはどんな式であっても構わない.
| Out[24]= |  |
|
Throw[value, tag]と
Catch[expr, form]の書式を使った場合,
Catchの返してくる値は
Throwに指定した第1引数
value になる.また,
Catchの書式を
Catch[expr, form, f]にすると,単なる
value ではなく,任意関数
fを作用させた式
f[value, tag]が返される.
| Out[25]= |  |
|
| Out[26]= |  |
|
| While[test,body] | 条件式test を判定し結果がTrueならば,式body を評価する.条件式が真でなくなるまで式の評価を繰り返す |
| For[start,test,incr,body] | 式start を評価し,条件式test を判定し結果がTrueならば,式body と式incr を評価する.条件式が真でなくなるまで2式の評価を繰り返す |
一般的なループ構成体
構造化アプローチを取るなら,
Do,
Nest,
FixedPoint等の関数を使いループを構築するとよい.また,
Throwと
Catchを併せて使えば,ループを監視,制御することも可能である.一方,構造にとらわれないループを作りたい場合は,関数
Whileや
Forを使うとよい.任意条件下で繰返し処理を停止できるループを構築することができる.
Whileループは,条件が満たされなくなるまで実行される. |
関数
Whileと
Forは,C言語の
whileと
forに構文上似ている.ただし,違いもある.例えば,
Forループのコンマとセミコロンの使い方はC言語と逆である.
Forループの簡単な例を見てみる. i++は iの値を1増加させる. |
少し複雑な Forループを作ってみよう.実行させると,ループは,条件式 i^2<10が満たされなくなった時点で終了する. |
Whileと
Forのループにおいて,まず評価されるのは条件式であり,その次に,本体の式が評価される.つまり,
Trueにならなければその時点で
Whileおよび
Forループは終了してしまう.このため,本体の式が評価されるのは条件式が
Trueになるときだけである.
ループの条件式にはもとから Falseが与えられているので,本体の式 Print[x]は完全に無視される. |
Whileや
Forによるループ,または,その他の手続きにおいて,式の評価は特定の手順に従って行われる.この評価手順を
Mathematica プログラムの実行における制御フローと呼ぶ.
一般に,制御フローはなるべく単純なものがよい.制御フローが,プログラム実行中に生成される特定値に依存すればするほど,プログラムの構造や動作が分かりづらいものになってしまう.
Whileや
Forのループ体を使うより関数的な構成体を使った方が,制御フローをより単純なものにできる.
Whileや
Forのループでは,制御フローがプログラム実行時の条件式の値に左右されてしまうので,ループが複雑な構成になりがちである.とは言え,ループであっても,制御フローは,普通その本体で与えられた式の値に依存するようなことはない.
それでも,場合によっては,本体の式の値に依存した形でフローを変えてやらなければならない.これを行う方法として,まず,関数的なプログラミングの考え方に適合した手法,つまり,関数
Throwと
Catchを使う方法が考えられる.さらに,
Mathematica にはC言語で提供される制御フローの操作用関数に相当した各種の関数が用意されているので,それらを利用するのも便利である.
フローの制御関数
tが 19を超え,制御が Break[]に達した時点でループが停止する. |
k<3のときは, Continue[]に制御が行き, t+=2の計算は実行されずにループが続行される. |
Return[expr]を使い,戻り値を指定することも可能である.ネストされた関数式から一挙に退避できることから,
Throwを非局所的なリターン機能と考えることもできる.エラー処理に利用すると便利である.
引数が5より大きいので,手続きでは最初の Returnが使われる.
| Out[34]= |  |
|
引数が負の数のときはエラー値 errorを返すように関数を定義する. |
| Out[36]= |  |
|
| Out[37]= |  |
|
Continue[]や
Break[]の関数はループの始点もしくは終点に制御を移すために使う.場合によっては,ループの外にある手続き内の任意の位置に制御を移したい.そのようなときは,転送先を関数
Labelであらかじめラベル指定をしてから関数
Gotoを使い実際に制御を移す.
注意してほしいが,
Gotoを使うには同じ手続き内に
Label指定がなければならない.ただし,
Goto指定を使うとフロー構造が入り組んだものになり,プログラムがどう機能しているのか分かりづらくなるので使用には注意が必要である.