IGORプログラミング:データの一括処理に至る道④:条件分岐

本稿では全てIGOR pro 8.0を基準にして解説を行いますが、ここで紹介するレベルであればバージョン違いはそれほど大きな問題にならないと思います。

条件分岐の必要性

条件分岐とは、文字通り「状況に応じて関数が分岐し、実行する処理の中身が変わること」です。
これは、単純に偶数と奇数で処理を変えたり、関数の引数に無効な値を入力された場合に警告文を出したりと、関数を作るうえでなくてはならないものです。

また、前回の記事では回数がわかっている場合の繰り返し処理について解説しました。

実は繰り返し処理には、前回のように「あらかじめ回数がわかっている場合ループ」と、「回数は不明だが特定の条件を満たすまで繰り返すループ」とがあります。例えば 1+2+3+ ・・・と順に足していく場合に「合計が初めて1000を超えるまで」繰り返す、というようなケースがこれにあたります。
このようなループについては次回に詳しく説明しますが、とりあえず現時点では条件分岐が様々なところに応用される、ということだけ頭に留めておいてください。

基本の条件分岐:if~endif

条件分岐を作るのはとても簡単で、「if (条件式)」と「endif」をセットで書くと、これらで挟まれた部分は条件式を満たすときにしか実行されません。
例えば

function iftest(a)
	variable a //引数aを数値として受け取る
	if(a>0)
		print "aは正の値です"
	endif
end

と書いてこの関数を実行してみると、

このようになって、引数aが0より大きい時には「aは正の値です」と表示されているのに、aが-3や0の時には何も表示されていません。
これは、aが-3や0の時にはifの中に書いたa>0を満たしていないので、if~endifの中の処理が実行されない(スキップされる)ためです。

条件式の書き方

条件式は二つの値を比較する形で次のように書くことができます。

a == 0 // aと0が等しい
a > 0  // aは0より大きい
a >= 0 // aは0以上
a < 0  // aは0より小さい
a <= 0 // aは0以下
a != 0 // aは0ではない

この例では変数と数値(0)を比較していますが、変数と変数、変数と関数の返り値、などでも比較できます。
注意が必要なのは両辺が等しい場合で、「==」のようにイコールを二つ重ねる必要があります。
これは、イコールが一つだとプログラム的には代入(variable a=0のような)と認識されてしまうためです。
条件分岐で思わぬエラーが出たら、まずイコールが一つになっていないかを確認してみると良いでしょう。

また、例えば「aが0、または2」や「aが0より大きく、かつ10より小さい」のように「または」「かつ」というのは、
「||」と「&&」で記述することができます。この場合、それぞれの条件をカッコでくくって、さらに全体をカッコでおおいます。
例えば次のようなコードを書いていろいろな条件で実行してみると

function iftest2(a)
	variable a
	if((a > 0) && (a < 10))
		print "aは0より大きく10未満です"
	endif
	
	if((a == 0) || (a == 2))
		print "aは0または2です"
	endif
end

確かに条件に応じて結果が変わっているのがわかります。
引数が2のとき、一つ目のifも二つ目のifも条件を満たしているので、どちらのprint文も実行されています。
また、引数が15の時、a>0は満たしていますがa<10ではないので「(a>0) かつ (a<10)」を満たさず、結果としてif文は実行されません。
なお、「&&(かつ)」のパターンは

function iftest3(a)
	variable a
	if(a>0)
		if(a<10)
			print "aは0より大きく10未満です"
		endif
	endif
end

のようにif文を二つ重ねる形で書いても同じ結果を得ることができます。

条件を満たさないときにのみ実行される処理

さて、これまでの単純なif~endifでは、条件を満たしたときにのみ処理が実行され、満たしていない場合にはその部分が単にスキップされていました。
では、例えば「aが0より大きい時には「aはゼロより大きいです」、小さい時には「aはゼロ以下です」」と表示させたい場合にはどうすればよいでしょうか?
まず、次のようなプログラムはうまくいきません。

function iftest4(a) // うまくいかないプログラム
	variable a
	if(a>0)
		print "aはゼロより大きいです"
	endif
	print "aはゼロ以下です"
end

これを実行してみると、

一見aがゼロ以下の時に「aはゼロ以下です」と表示されているように見えますが、aがゼロより大きい時には「aはゼロより大きいです」「aはゼロ以下です」と両方表示されてしまいます。
これは、a<0のときはif~endifがスキップされて「print “aはゼロ以下です”」だけが実行されるのですが、この文はif~endifの外に書かれているためにaの値にかかわらず表示されてしまうことが原因です。
a>0の時はif~endifのprint文も、最後のprint文も両方実行されてしまうんですね。

これを避ける手段の一つには、毎回if~endifを書くというものがあります。つまり、

function iftest5(a)
	variable a
	if(a>0)
		print "aはゼロより大きいです"
	endif
	if(a<=0)
		print "aはゼロ以下です"
	endif
end

のように、まず「aが0より大きいかどうか」、次に「aが0以下かどうか」を判別するためのif文を書くというやり方です。

この方法は間違ってはいないのですが、単に「一つ目のif文が条件を満たさない場合」というのを示すだけにしてはプログラムが読みにくくなるのと、
a>0の逆条件が「a < 0」ではなく「a <= 0」であることを気にしてプログラムを書かなければならないのでミスしやすいという問題があります。

そのような場合には、「条件を満たさない場合にのみ実行される」処理を専用の書き方で追加してあげると便利です。
このような処理が必要な場合、if(条件式)~else~endifという書き方をします。例えば次のようになります。

function iftest6(a)
	variable a
	if(a>0)
		print "aはゼロより大きいです"
	else
		print "aはゼロ以下です"
	endif
end

この関数iftest6は、if文を二つ並べたiftest5と全く同じ処理をします。a>0の場合にはif~elseの間の処理だけを実行してendifに飛び、そうでない場合には逆にelse~endifの間の処理だけを実行します。

この例では単に結果を表示しているだけなのでどちらを使ってもほとんどみやすさに違いはありませんが、
実際のプログラミングでは変数aの意味合いや実現したい処理によって「aが0以下であることが重要な場合」と「a>0を満たさないということが重要」な場合があり得ます。
どちらも論理的には同じことですが、前者ならif(a>0)とif(a<=0)を並べて書く(つまりiftest5(a)の書き方の)ほうが、後者ならif(a>0)~else(つまりiftest6(a)の書き方)のほうが、書いてあることと意味合いが一致するので読みやすくなります。

3つ以上の条件分岐:条件を満たさないときにさらに条件分岐をする

さて、今のif~else~endifは二つの分岐しかできませんでした。では、a>0なら「aは正の値です」、a==0なら「aはゼロです」、a<0なら「aは負の値です」という風に三パターン(以上)に分岐させたい場合はどうすればよいでしょう?

一つの書き方は、まずif~else~endifにしておいて、さらにelse~endifの中にif~else~endif文を重ねるやり方です。例えばこうですね。

function iftest7(a)
	variable a
	if(a>0)
		print "aは正の値です"
	else
		if(a==0)
			print "aはゼロです"
		else
			print "aは負の値です"
		endif
	endif
end

このコードは確かに正しく動きます。a>0でない場合まず一つ目のif文ではelseの中に入ります。
そこで改めてa==0かどうかを調べて、もしそれも満たしていない場合はまたelseに入るので「aは負の値です」が表示されます。

ただ、このように書いてしまうと3方向への分岐であるということがわかりにくくなります。
このような場合に使うのが、if(条件式)~elseif(条件式)~else~endifという書き方です。例えば次のような書き方になります。

function iftest8(a)
	variable a
	if(a>0)
		print "aは正の値です"
	elseif(a==0)
		print "aはゼロです"
	else
		print "aは負の値です"
	endif
end

この場合、まず一つ目のif文でa>0かどうかを調べ、もし条件を満たしていないならelseifの中のa==0を満たしているかどうかを調べます。
もしそれも満たしていないなら最後のelse文の中身を実行します。

ちなみに、最後のelse文を書かずにif~elseif~endifで終わらせることもできます。
この場合、最初のif条件も次のelseif条件も満たさない場合には何も実行されずにendifに飛びます。
例えば次のようなコードを書いた場合は、

function iftest9(a)
	variable a
	if(a>0)
		print "aは正の値です"
	elseif(a==0)
			print "aはゼロです"
	endif
end

aが3の時や0のときはそれぞれその値に応じた処理が実行されているのに対し、a<0の時は何も実行されていないことがわかります。

また、elseifを何個もつなげてif~elseif~elseif~elseif~else~endifというような書き方もできます。

あまりに条件分岐が複雑になるとプログラムを読むのが難しくはなってしまいますが、もし必要な場合は使ってみてください。

条件分岐と絡めて覚えておくとよい命令①:mod(p,q)

さて、条件分岐を使いこなすため、よく条件分岐とセットで使うIGOR関数を覚えておきましょう。
一つ目は、「mod(p, q)」です。この関数は、「pをqで割った余り」を返します。
例えば「mod(10,2)」はゼロ、「mod(7,2)」となります。
つまり、これをmod(a,2)のように使えばaが偶数か奇数かを簡単に判別することができます。
サンプルコードは次の通りです。

function modtest(a)
	variable a
	if(mod(a,2)==0)
		print "aは偶数です"
	else
		print "aは奇数です"
	endif
end

また、例えばfor文で極めて大きい繰り返しをする場合、処理中IGORが固まってしまって処理が無事に進んでいるのか、それとも何かエラーを出して止まってしまっているのかの判別が難しいことがあります。
このような場合、ループ分の中で次のように書いてあげることで進捗がモニターできて安心です。

function looptest_proceedcheck()
	variable i
	for(i=0; i < 100000000; i+=1) // 大きなループ
		//時間のかかる処理
		
		if(mod(i,10000)==0) // iが10000で割り切れるときだけ実行
			printf "%d\r",i; DoUpdate
		endif
	endfor
end

printf分にあるセミコロン「;」は文の区切りで、改行と同じ役割を果たします。
DoUpdateはIGOR固有の命令文で、直前のprintfを即座に実行するようIGORに命令しています。
実は画面の書き換えというのはパソコンにとってとても時間のかかる処理なので、
IGORは時々繰り返し処理の間は画面を書き換えず、すべてが終わってから一気に書き換えようとすることがあります。
命令があるたびに毎回書き換えるか、それとも保留しておいて一気に書き換えるかはIGORが臨機応変に判断するのですが、
ここでDoUpdateと書いておくことで必ず画面を更新してくれます。
ループの中で進捗確認する場合、iの表示がループ後に一気にされても無意味なので、おまじないと思って毎回つけておきましょう。
(ちなみに一時的に画面更新を保留してほしい場合、DelayUpdateという命令を使うことができます)

今回はiが10000増えるごとにprintf命令を呼びだしていますが、ここの数値は当然状況に応じて変えることになります。
ただし、if文を省いて単純に毎回printf “%d”\r, iするのはよくありません。

というのは、単純に1億回も数値が表示されても目で追いきれない&邪魔なのと、なにより画面描画に時間がかかるからです。
試しに次のコードを実行してみましょう。

function displaytest()
	variable i
	print "時々表示"
	print time(); DoUpdate
	for(i=0;i<100000;i+=1)
		if(mod(i,10000)==0)
			printf "%d\r",i; DoUpdate
		endif
	endfor	
	print time()
	
	print "毎回表示"; DoUpdate
	for(i=0;i<100000;i+=1)
		printf "%d\r",i; DoUpdate
	endfor
	print time()
	print "終了"
end

10万回の繰り返しのうち、10000回に一回だけ進捗を書きだす場合と、毎回進捗を書きだす場合の時間を比較するプログラムです。

筆者の環境でこれを実行すると

中略

という結果になりました。

10000回に一回だけ表示する場合には10万回のループで1秒もかかっていないのに対し、毎回表示させてしまうと1分半もかかってしまっています。

このように、過度の画面更新は繰り返し処理の大敵ですので、ぜひ気を付けてください。
(同じ理由で、画面にwaveがグラフ表示された状態で繰り返し処理的にそのwaveをいじると、グラフの更新に長時間を取られてしまいます。
そのような場合はDelayUpdateを忘れないようにしましょう)

条件分岐と絡めて覚えておくとよい命令②:cmpstr(str1, str2)

もう一つの重要な関数はcmpstrです。おそらくcompare stringsの略と思いますが、これは文字列を比較するための関数です。

実はIGORでは、文字列同士を単純にif文で比較することができません
つまり、if(str==”abc”)のように書くことができないということです。

function compare_str1(str)//うまくいかない関数
	string str// 引数strを文字列型(string型)で受け取る
	if(str=="abc")
		print "strの中身はabcです"
	endif
end

この関数はコンパイルの時点でエラーが出てしまいます。

このようなことを実現したい場合に、cmpstrという関数を使って次のように書きます。

function compare_str2(str)//うまくいかない関数
	string str// 引数strを文字列型(string型)で受け取る
	if(cmpstr(str,"abc")==0)
		print "strの中身はabcです"
	endif
end

cmpstr(str1, str2)は、二つの文字列str1とstr2が等しい場合にのみゼロを返し、そうでない場合にはゼロ以外の値を返します。
ですので、cmpstrが0かどうかを判別することで、文字列同士の比較が実現します。

wave名のチェックなど、文字列を比較する場面は多いので、ぜひ活用してください。

今回の記事は以上となります。少し長かったですが大丈夫でしたでしょうか。

次回は回数不明のループについてになります。

Follow me!

コメントを残す

メールアドレスが公開されることはありません。