Macromedia Flash非公式テクニカルノート

素数を調べる(ループ処理2)

ID: FN0110002 Product: Flash

Platform: All
Version: 5.0 and above

'for'や'while'アクションは、終了条件を指定して、繰り返しループ処理を行います。これらのアクションを使って、指定された整数までの素数を調べてみましょう。素数は、1より大きい整数でかつ1とそれ自身でしか割切れない数のことです。1が含まれないことに、注意が必要です。

'for'と'while'アクションの基本的な使い方については、「1から連番の配列をつくる(ループ処理)」および「ActionScript辞書」をご参照ください。

1. 素数を配列で返す関数
引数として渡した整数までのすべての素数を、配列で返す関数のサンプルです。ループ処理の応用事例として、'break'や'continue'アクションも使用しています。

関数を以下のように実行すると、10までの素数"2,3,5,7"が出力ウィンドウに表示されます。

// 関数のテスト例
trace (xGetPrimeNumbers(10));
// 出力ウィンドウの表示
2,3,5,7

// [素数を配列で返す関数]
// 引数nMax: 素数を調べる整数の最大値
function xGetPrimeNumbers (nMax) {
   // [1]結果を格納する配列を初期化
   var lResult = new Array();
   // [2]2から指定された整数までの素数をすべて調べる
   for (var i = 2; i<=nMax; i++) {
     // [3]変数の初期化
     var nCount = 0;
     var n = 1;
     // [4]割る数が割られる数より大きくなるまでループ処理で調べる
     while (i>=n) {
       // [5]1と自分自身の数以外で割り切れたら素数でない
       if (nCount>2) {
         break;
       }
       // [6]割り切れた整数の数をカウント
       if (!(i%n)) {
         nCount++;
       }
       // [7]割る数を1増やす
       n++;
     }
     // [8]割り切れた整数の数が2より大きければ素数でない
     if (nCount>2) {
       continue;
     }
     // [9]素数を結果格納の配列に追加
     lResult.push(i);
   }
   // [10]素数の配列を返す
   return lResult;
}

スクリプトについて、コメントでつけたナンバーにしたがって、簡単に解説します。使用しているメソッドやアクション、演算子などについて、詳しくは「ActionScript辞書」をご参照ください。

[1] まず、第1ステートメントでは、素数を格納して、関数の値として返すための空の配列を作成しています。

[2] つぎに、'for'アクションを使って、素数を調べます。素数に1は含まれませんので、変数の初期値は2、終了値は引数で渡された整数値で、整数を順に加算して調べることになります。

変数iは、'function'内でのみ使用しますので、'var'アクションでローカル変数宣言をしておきます。'var'アクションは、'for'ループの初期値設定でも使用することができます。この宣言をしなくても、スクリプトの動作としては、問題ありません。ただ、この'function'を記述したタイムライン(MovieClip)にグローバル変数として値が残ってしまいます。不要なグローバル変数を設定することは、できれば避けましょう。

[3] 他に変数を2つ使いますので、その値も初期化します。nCountは、割切れる整数の個数です。1とそれ自身でしか割り切れないのが素数です。したがって、この値が2であれば素数、それ以上なら素数でないということになります。これから調べるわけですから、初期値は0です。

nは、調べる値を割る整数です。1からスタートして、指定された最大値まで順に加算して、調べる数値が割切れるかどうかをチェックしていく訳です。1では、すべての整数が割切れるに決まっていると思われた方もあるでしょう。ただ、ここでは素数の定義にしたがって、まずスクリプトを書いてみることにします。効率的なスクリプティングについては、後述します。

これらの2つの変数も、'var'アクションでローカル変数の宣言をしています。

[4] nをループ処理で加算しながら、調べる値iが割切れるかどうかを調べます。ここでは、'while'アクションを使っています。終了条件は、割る数nが割られる値iより大きくなることです。

終了条件として、nを引数で指定された最大値nMaxになるまでとしても、間違いではありません。ただ、割られる値よりも割る数が大きい場合、商は0、余りが割られる値自身になるので、割切れることはありません。したがって、nがiと同じ値になるまで調べれば十分なのです。

[5] nを加算しながらループ処理を進めていったとき、割切れた整数の個数が2を超えた場合、現在調べている値iは素数ではありません。ですから、これ以上nを加算して調べる必要がありません。'break'アクションで、'while'ループの処理を終了します。そうしますと、'while'アクションのつぎの処理[8]以降が実行されます。

[6] iをnで割った余りを調べます。ここで'if'条件が論理式になっていません。条件式は論理式でなくても、評価は可能なのです。iをnで割った余り(i%n)は、数値になります。数値を条件として評価したとき、0(と'NaN')以外は'true'と評価されます(「if (condition=1)」参照)。さらに、'!(符号反転(NOT))'演算子があるために、'true'と'false'が反転します。したがって、この条件式は、余りが0のとき'true'、そうでないとき'false'と評価されます。つまり、iがnで割切れたときに、nCountを加算する処理になる訳です。

[7] 割る数nを1加算して、つぎのループ処理を行います。

[8] 'while'ループで、ひとつのiの値に対して、nで割り切れるかどうかを調べ終わったときの処理です。割切れた整数の個数が2より大きいときは、調べたiが素数ではないことになります。その場合には、'continue'アクションにより、以降のステートメントは実行せずに、'for'ループのつぎの値iを調べることになります。

[9] [8]でつぎのループ処理に移行しなかったということは、nCountが2以下、つまりiが1とその値自身でしか割切れない素数だということを意味します。そこで、その素数iを結果の配列に加えます。そして、'for'ループの処理がまだ終わっていなければ、つぎの値iを調べます。

[10] 'for'アクションのループ処理が終了し、すべての素数が調べ終わりましたので、結果の配列を返します。

2. スクリプトの処理を考える
'break'や'continue'アクションは、多くの場合それを使わないスクリプティングが可能です。これらのアクションを使う意味は、まず例外処理であることを明確化することでしょう。素数の例でいえば、調べた値が1とその数自身以外に割切れなかったときのみ、その先の処理に進むというロジックを、スクリプティングとして明示したいときに、これらのアクションが役立ちます。

もうひとつは、'if'アクションが何階層もの入れ子構造(ネスト)になるときです。例外を予め除外するスクリプトを記述しておけば、その後の処理がわかりやすくなります。

今回の素数を調べる関数の処理を、もう少し効率化してみたいと思います。

書替えたスクリプトは、以下のとおりです。おもな変更部分には、コメントを挿入してあります。

// [素数を配列で返す関数]
// 引数nMax: 素数を調べる整数の最大値
function xGetPrimeNumbers (nMax) {
   var lResult = new Array();
   // [A]不適切なパラメータチェック
   // 引数が2より小さいか数値でない場合
   if (nMax<2 || typeof nMax != "number") {
     return lResult;
   }
   for (var i = 2; i<=nMax; i++) {
     var nCount = 0;
     // [B]割る数は2から開始
     var n = 2;
     // [C]調べる値の範囲を制限
     // 割る数が割られる値の1/2を超えたらつぎの数へ
     while (i/2>=n) {
       // [D]割切れる数があったら調べるのをやめる
       if (!(i%n)) {
         nCount++;
         break;
       }
       n++;
     }
     // [E]割切れる数がなかったら素数に加える
     if (nCount == 0) {
       lResult.push(i);
     }
   }
   return lResult;
}

[A] 渡された引数のチェックを加えました。パラメータ(引数)の確認を行うと、'function'の汎用性が高まるからです。

引数は、2以上の数値である必要があります。引数に不適切な値が渡されることはないという前提条件があれば、この処理は省略して構いません。

[B] すべての整数は、1で割切れます。したがって、素数の確認には、割る数として2以上を指定すればよいでしょう。

[C] 値を調べる条件として、割る数nは調べる値iの2分の1までとしました。1の[4]では、調べる数自身の値を超えるまで、nを加算しています。けれど、割る数nが割られる数の半分を超えると、商は1になり、余りが出ます。余りが0となるのは、割る数が割られる値と一致したときです。したがって、割る数が調べる値の2分の1を超えると、その値以外には割切れる整数がないということになるのです。

[D] 'for'および'while'ループ処理の条件指定で、1やその値自身で割ることは、もはや除外しています。ということは、割切れる数がひとつでもあれば、その値は素数ではないということになります。そこで、変数nCountの値を加算したうえで、'while'ループの処理を終了し、それ以上調べるのをやめることにしました。前のサンプルの[5]と[6]の処理を、組合わせたような結果になります。

[E] 1の[8]では、素数でなかったら、'continue'アクションによりつぎの処理に進めていました。ここでは、原則と例外を入換え、素数だったら結果の配列にその素数の値を加えることにしました。

_____

作成者: 野中文雄
協力者: 植木友浩
更新日: 2002年1月4日 2のサンプルスクリプトで[D]の処理部分を変更
作成日: 2001年12月31日


© 2001 and beyond Fumio Nonaka All rights reserved.