サイトトップ

Director Flash 書籍 業務内容 プロフィール

Optimizing Performance of ActionScript 3.0

Chapter 02 条件で処理を分ける

□02-01 条件の順序を考える

ifステートメントを始めとする条件による処理の切り分けに当たっては、まず論理の組立てを明らかにしなければなりません。とくに、複数の条件を組合わせる場合には、条件の順序に気を配ります。条件の順序を工夫することで、論理構造が見やすくなり、漏れや誤りが防げます。構造がはっきりすれば、無駄を省き、より洗練されたスクリプトに改めることもできます。

    【複数の条件を組合わせた処理では】
  1. 論理構造を考えて条件の順序を工夫する
  2. 論理構造に照らしてスクリプトを改善する

お題としては、適度に複雑な「うるう年」の判定を採上げます。関数を定義して、引数に渡された整数の年がうるう年ならtrue、そうでなければfalseを返すことにします。うるう年かどうかを決める条件はつぎのとおりです。

    【うるう年の条件】
  1. 原則として、4で割切れる年はうるう年
  2. ただし例外として、100で割切れる年は普通の年(平年)
  3. さに例外として、400で割切れる年はうるう年

02-01-01 条件の組立てはできるだけ単純にわかりやすく
数が割切れるかどうかは、割った余りを求める剰余演算子%で確かめられます。上記「うるう年の条件」をそのままifステートメントに引き写すと、つぎのスクリプト02-01-001のようになるかもしれません。例外の条件はifステートメントを入れ子にして扱っています。

スクリプト02-01-001【△】組合わされた条件をifステートメントの入れ子で扱う
  1. function xIsLeapYear(nYear:int):Boolean {
  2.   if (nYear % 4 == 0) {   // 4で割切れる
  3.     if (nYear % 100 == 0) {   // 100で割切れる
  4.       if (nYear % 400 == 0) {   // 400で割切れる
  5.         return true;
  6.       } else {
  7.         return false;
  8.       }
  9.     } else {
  10.       return true;
  11.     }
  12.   } else {
  13.     return false;
  14.   }
  15. }

スクリプトに誤りはありません。関数(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で割切れる年」かどうかで、truefalseを仕分ければ終わりです。

スクリプト02-01-002【△】例外から処理して整理(最適化はまだされていない)
  1. function xIsLeapYear(nYear:int):Boolean {
  2.   if (nYear % 400 == 0) {   // 400で割切れる(みかんのS)
  3.     return true;
  4.   } else if (nYear % 100 == 0) {   // 100で割切れる(みかんのM)
  5.     return false;
  6.   } else if (nYear % 4 == 0) {   // 4で割切れる(みかんのL)
  7.     return true;
  8.   } else {
  9.     return false;
  10.   }
  11. }

みかん選別機に習ったおかげで、条件の入れ子がなくなって、すっきりとした流れになりました。もっとも、まだ最適化はできます。ただ、簡潔に整理されたので、その検討も加えやすいでしょう。

さて、このスクリプト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ステートメントで判定
  1. function xIsLeapYear(nYear:int):Boolean {
  2.   if ((nYear % 4 == 0 && nYear % 100 != 0)   // 4で割切れ、かつ100で割切れない
        || nYear % 400 == 0) {   // または400で割切れる
  3.     return true;
  4.   } else {
  5.     return false;
  6.   }
  7. }

ステートメントの数そのものは、とても少なくなりました。しかし、組合わせた条件の論理は、わかりやすいとはいい難いでしょう。実際、最適化もされていません。このスクリプト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【○】整理した条件の順序を最適化
  1. function xIsLeapYear(nYear:int):Boolean {
  2.   if (nYear % 4 != 0) {   // 4で割切れない
  3.     return false;
  4.   } else if (nYear % 100 != 0) {   // 100で割切れない
  5.     return true;
  6.   } else if (nYear % 400 != 0) {   // 400で割切れない
  7.     return false;
  8.   } else {
  9.     return true;
  10.   }
  11. }

trueと評価される可能性の高い順に条件を並べました。したがって、ifステートメントを早く抜ける率が高くなり、その分処理は速まります。もっとも、いきなり最適化した条件を組立てようとしても、無理がありますし、その必要もありません。まずは、前掲スクリプト02-01-002のように、できるだけ単純でわかりやすい流れにすることです。すると、どこに無駄があるのか、どこで時間が費やされているのか、見つけやすくなります。そのうえで、どこをどう直すのか、じっくりと考えてみるのが、よい結果を得ることにつながります。

Tips 02-01-001■条件のブール(論理)値評価
ifステートメントなどの条件には、多くの場合関係演算子や等価・不等価演算子を用いた論理式が指定されます。論理式は、truefalseかのブール(論理)値を返します。

しかし、ブール値を返さない式であっても、条件として指定することはできます。「式」には、ひとつの変数や単純な数値、文字列なども含まれます。そして、ifステートメントなどの条件は、必ずBoolean型に変換されます。つまり、シンタックスエラーを生じないかぎり、truefalseのどちらかとして扱われるのです。

条件に指定した式がブール値以外の値を返した場合、それを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【○】条件に数値演算の式をそのまま記述
  1. function xIsLeapYear(nYear:int):Boolean {
  2.   if (nYear % 4) {
  3.     return false;
  4.   } else if (nYear % 100) {
  5.     return true;
  6.   } else if (nYear % 400) {
  7.     return false;
  8.   } else {
  9.     return true;
  10.   }
  11. }

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)のまま


値がnullの場合に空文字列""を設定


文字列が設定されている場合はそのまま

変数値がデフォルトのnullのままでは、Stringクラスのプロパティやメソッドにアクセスできない。そこで、nullの場合には、空文字列""を設定する。


Word 02-01-001■オペランド
プログラミングにおいて、演算の対象となる式(値や変数を含みます)のことを「オペランド」(operand)といいます。「被演算子」と呼ばれることもあります。たとえば、n + 10という式では、+が加算演算子で、変数nと値10がオペランドです。

[*筆者用参考] e-Words「オペランド」、IT用語辞典バイナリ「オペランド


[*筆者用参考]

「ActionScript 3.0におけるパフォーマンス向上のヒント」03「条件判定を考える

Testing leap years - wonderfl build flash online



作成者: 野中文雄
作成日: 2011年2月24日


Copyright © 2001-2011 Fumio Nonaka.  All rights reserved.