ifステートメントを始めとする条件による処理の切り分けに当たっては、まず論理の組立てを明らかにしなければなりません。とくに、複数の条件を組合わせる場合には、条件の順序に気を配ります。条件の順序を工夫することで、論理構造が見やすくなり、漏れや誤りが防げます。構造がはっきりすれば、無駄を省き、より洗練されたスクリプトに改めることもできます。
【複数の条件を組合わせた処理では】
- 論理構造を考えて条件の順序を工夫する
- 論理構造に照らしてスクリプトを改善する
お題としては、適度に複雑な「うるう年」の判定を採上げます。関数を定義して、引数に渡された整数の年がうるう年ならtrue、そうでなければfalseを返すことにします。うるう年かどうかを決める条件はつぎのとおりです。
【うるう年の条件】
- 原則として、4で割切れる年はうるう年
- ただし例外として、100で割切れる年は普通の年(平年)
- さに例外として、400で割切れる年はうるう年
○02-01-01 条件の組立てはできるだけ単純にわかりやすく
数が割切れるかどうかは、割った余りを求める剰余演算子%で確かめられます。上記「うるう年の条件」をそのままifステートメントに引き写すと、つぎのスクリプト02-01-001のようになるかもしれません。例外の条件はifステートメントを入れ子にして扱っています。
スクリプト02-01-001【△】組合わされた条件をifステートメントの入れ子で扱う
- function xIsLeapYear(nYear:int):Boolean {
- if (nYear % 4 == 0) { // 4で割切れる
- if (nYear % 100 == 0) { // 100で割切れる
- if (nYear % 400 == 0) { // 400で割切れる
- return true;
- } else {
- return false;
- }
- } else {
- return true;
- }
- } else {
- return false;
- }
- }
|
スクリプトに誤りはありません。関数(xIsLeapYear())の引数に年を整数で渡せば、うるう年ならtrue、普通の年であればfalseが返されます(図02-01-001)。このスクリプトで困るのは、何より入れ子の条件がわかりにくいことです。論理が捉えづらいと、改善も難しくなります。
図02-01-001■入れ子のifステートメントで例外を扱う

関数の引数に渡した年がうるう年ならtrue、平年であればfalseが返される。
|
○02-01-02 みかん選別機に学ぶ条件の順序
ifステートメントは、頭から順に条件を確かめます。そして、trueと評価される条件があれば、そのステートメントブロック{}内を処理して、後の条件は見もせずに、ifステートメントから抜けます。そこで、条件の順序が大切になります。
たとえば、農家の収穫したみかんをS/M/Lに仕分ける機械があります。仕組みは単純で、ベルトコンベヤーでみかんが流れる先には、3つの大きさの違う穴が開いています(図02-01-002)。もちろん、一番手前がSです。小さいみかんはこの穴から落ちますので、その先にはM以上のみかんしか流れません。そして、つぎにMの穴、最後にLの穴と続きます。間違っても、手前にLの穴を開けてはいけません。つまり、順序が大切です。
図02-01-002■みかんのS/M/Lを仕分ける選別機
[編集用注] 以下の画像2点を参考に、イラストを起こしていただくことはできますか。

メーカーのみかん選果機画像 |

拙著『ActionScript 3.0プロフェッショナルガイド』のイラスト |
コンベヤーの先に、S、M、Lの順に穴が開いている。
|
うるう年のような組合わせられた条件は、例外から処理すると、すっきりとまとめられることが少なくありません。みかん選別機に習って整理したのが、以下のスクリプト02-01-002です。
前記「うるう年の条件」のうち例外中の例外である3「400で割切れる年」が、みかんのSに当たります。間違いなくうるう年ですので、trueを返して処理を抜けます。残ったみかんの中のMは例外となる条件2の「100で割切れる年」です。「400で割切れる年」はもう残っていませんので、安心してfalseを返します。あとはみかんのL、条件1の「4で割切れる年」かどうかで、trueとfalseを仕分ければ終わりです。
スクリプト02-01-002【△】例外から処理して整理(最適化はまだされていない)
- function xIsLeapYear(nYear:int):Boolean {
- if (nYear % 400 == 0) { // 400で割切れる(みかんのS)
- return true;
- } else if (nYear % 100 == 0) { // 100で割切れる(みかんのM)
- return false;
- } else if (nYear % 4 == 0) { // 4で割切れる(みかんのL)
- return true;
- } else {
- return false;
- }
- }
|
みかん選別機に習ったおかげで、条件の入れ子がなくなって、すっきりとした流れになりました。もっとも、まだ最適化はできます。ただ、簡潔に整理されたので、その検討も加えやすいでしょう。
さて、このスクリプト02-01-002の関数(xIsLeapYear())で、世界中の人たちの生まれた年を調べるとしましょう。年による出生数の違いを考えなければ、およそ3/4の(4で割切れない)人びとが第9行目のステートメントでfalseを返されます。つまり、3つの条件すべてを確かめます。逆に、最初の条件(第2行目)で抜けるのは、400年に1度のうるう年、生存者の中では2000年生まれの人たちだけです。
ベルトコンベヤーの最後まで残るみかんが多いほど、処理は増えます。大多数を占める3/4の人たちをもっと早く抜けさせられれば、負荷が減らせるのです。
○02-01-03 条件の組合わせは最適化とは別
ifステートメントではなく、条件を複数組合わせることもできます。ふたつの条件に対して、||(論理和)演算子はどちらかがtrueと評価されるとき、&&(論理積)演算子はふたつともにtrueと評価されるとき、組合わせた条件がtrueとして扱われます(それ以外はfalse)。これらふたつの演算子を用いると、うるう年がひと組のif/elseステートメントで調べられます(スクリプト02-01-003)。
スクリプト02-01-003【△】&&および||演算子を用いてとひと組のif/elseステートメントで判定
- function xIsLeapYear(nYear:int):Boolean {
- if ((nYear % 4 == 0 && nYear % 100 != 0) // 4で割切れ、かつ100で割切れない
|| nYear % 400 == 0) { // または400で割切れる
- return true;
- } else {
- return false;
- }
- }
|
ステートメントの数そのものは、とても少なくなりました。しかし、組合わせた条件の論理は、わかりやすいとはいい難いでしょう。実際、最適化もされていません。このスクリプト02-01-003の関数(xIsLeapYear())の処理を、具体的に考えてみましょう。
たとえば、2004年は4で割切れ、かつ100で割切れません。つまり、&&条件がtrueと扱われますので、後の||条件(400で割切れる)を調べることなく、関数はtrueを返します(第3行目)。
ところが、2011年は4で割切れません。したがって、&&条件はfalseの扱いとなりますので、つぎの||条件の評価に移ります。しかし、4で割切れない数が400で割切れるはずはありません。つまり、無駄な処理です。前節の世界中の人たちの誕生年を調べる例でいえば、大多数の3/4についてこれが当てはまります。ひとつ目の条件(4で割切れる)がfalseなら、関数からはただちにfalseを返したいところです。
○02-01-04 条件の順序を最適化する
複数の条件の組合わせは、ベン図で表すとわかりやすくなります(図02-01-003)。前記「うるう年の条件」は、同心円状になります。つまり、3の「400で割切れる」年は、つねに2の「100で割切れる」年に含まれます。そして、「100で割切れる年」は、さらに「4で割切れる年」に含まれてしまいます。外から順に取出せれば、それが最適化された条件の組立てです。
図02-01-003■うるう年の条件を表すベン図
3つの条件が同心円で示される。外から順に条件で取出したい。
|
ここでまたみかん選別機を思い浮かべながら、最適化された条件の順序を考えましょう。まず、(1)もっとも多い(3/4を占める)「4で割切れない」年を取出します。すると、4で割切れる年だけが残って、コンベヤーを先に進みます。そこでつぎに、(2)「100で割切れない」という条件を与えると、原則のうるう年が抜けます。残るのは100で割切れる年ですから、(3)「400で割切れない」かを確かめれば、例外の年が切り分けられます。この条件の順序にしたがったのが、つぎのスクリプト02-01-004です。
スクリプト02-01-004【○】整理した条件の順序を最適化
- function xIsLeapYear(nYear:int):Boolean {
- if (nYear % 4 != 0) { // 4で割切れない
- return false;
- } else if (nYear % 100 != 0) { // 100で割切れない
- return true;
- } else if (nYear % 400 != 0) { // 400で割切れない
- return false;
- } else {
- return true;
- }
- }
|
trueと評価される可能性の高い順に条件を並べました。したがって、ifステートメントを早く抜ける率が高くなり、その分処理は速まります。もっとも、いきなり最適化した条件を組立てようとしても、無理がありますし、その必要もありません。まずは、前掲スクリプト02-01-002のように、できるだけ単純でわかりやすい流れにすることです。すると、どこに無駄があるのか、どこで時間が費やされているのか、見つけやすくなります。そのうえで、どこをどう直すのか、じっくりと考えてみるのが、よい結果を得ることにつながります。
Tips 02-01-001■条件のブール(論理)値評価
ifステートメントなどの条件には、多くの場合関係演算子や等価・不等価演算子を用いた論理式が指定されます。論理式は、trueかfalseかのブール(論理)値を返します。
しかし、ブール値を返さない式であっても、条件として指定することはできます。「式」には、ひとつの変数や単純な数値、文字列なども含まれます。そして、ifステートメントなどの条件は、必ずBoolean型に変換されます。つまり、シンタックスエラーを生じないかぎり、trueかfalseのどちらかとして扱われるのです。
条件に指定した式がブール値以外の値を返した場合、それをBoolean型に変換した値は、下表02-01-001のとおりです。
表02-01-001■ブール値以外の式をBoolean型に変換した値
式の値 |
Boolean型に変換された値 |
0
|
false
|
NaN
|
false
|
0とNaN以外の数値
|
true
|
空文字列("")
|
false
|
空でない文字列
|
true
|
null
|
false
|
undefined
|
false
|
クラスのインスタンス
|
true
|
条件に指定した式の値が数値の場合、「0とNaN以外」はtrueとして扱われます。すると、前掲スクリプト02-01-004のif条件は、つぎのように数値演算の式をそのまま書くこともできます(スクリプト02-01-005)。ただし、処理は速くはなりません。
スクリプト02-01-005【○】条件に数値演算の式をそのまま記述
- function xIsLeapYear(nYear:int):Boolean {
- if (nYear % 4) {
- return false;
- } else if (nYear % 100) {
- return true;
- } else if (nYear % 400) {
- return false;
- } else {
- return true;
- }
- }
|
|
Tips 02-01-002■||(論理和)および&&(論理積)演算子の戻り値
||(論理和)および&&(論理積)演算子は、ともにふたつの式を演算の対象(「オペランド」と呼ばれます。後掲Word 02-01-001)とし、それらを条件つまりブール(論理)値として評価します(前掲Tips 02-01-001「条件のブール(論理)値評価」参照)。
式1 || 式2
式1 && 式2
しかし、演算結果として返される値は、ブール値にかぎられません。正確には、どちらかのオペランド(式1もしくは式2)の値です。||および&&演算子について、各オペランドの評価に対応する戻り値は、それぞれつぎの2表02-01-002ならびに02-01-003のとおりです。
表02-01-002■||演算子による式の評価と戻り値
式1の評価 |
式2の評価 |
戻り値 |
true |
評価せず |
式1の値 |
false |
true |
式2の値 |
false |
false |
式2の値 |
表02-01-003■&&演算子による式の評価と戻り値
式1の評価 |
式2の評価 |
戻り値 |
true |
true |
式2の値 |
true |
false |
式2の値 |
false |
評価せず |
式1の値 |
もちろん、オペランドには論理式を指定することが多く、論理式はブール値を返すので、その場合演算結果はブール値になります。ブール値でない戻り値を用いる例としては、変値の値がnullのときに初期値を与える場合などです。
// 変数宣言のみのときデフォルト値はnull
var my_str:String; // = "test";
// 値があればそのままで、nullのときは空文字列""を設定
my_str = my_str || "";
|
図02-01-004■デフォルト値がnullのString型変数に初期値を与える
変数値がデフォルトのnullのままでは、Stringクラスのプロパティやメソッドにアクセスできない。そこで、nullの場合には、空文字列""を設定する。
|
|
Word 02-01-001■オペランド
プログラミングにおいて、演算の対象となる式(値や変数を含みます)のことを「オペランド」(operand)といいます。「被演算子」と呼ばれることもあります。たとえば、n + 10という式では、+が加算演算子で、変数nと値10がオペランドです。
[*筆者用参考] e-Words「オペランド」、IT用語辞典バイナリ「オペランド」
|
作成者: 野中文雄
作成日: 2011年2月24日