サイトトップ

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

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

配列を偏りなくランダムに並べ替える

ID: FN1110004 Product: Flash CS3 and above Platform: All Version: 9 and above/ActionScript 3.0

予め決まったデータから、ランダムに値を得たいことがあります。とくに、複数の値を重複なく取出したいときには、値を配列に入れてランダムに並べ替えるのがスマートです。シャッフルしたカードを配るのと同じで、あとは頭から順に使えばよいからです。


01 配列をランダムに並べ替える
配列エレメントをランダムに並べ替えるための関数を定義してみましょう(スクリプト001)。関数名はxShuffleArray()とし、引数には並べ替える対象の配列を渡します(第1行目)。配列は参照渡しですので、とくに戻り値は必要ありません。引数の配列は直接並べ替えられるからです。

スクリプト001■配列エレメントをランダムに並べ替える関数
  1. function xShuffleArray(my_array:Array):void {
  2.   var i:int = my_array.length;
  3.   while (i) {
  4.     var nRandom:int = Math.floor(Math.random()*(i--));
  5.     var temp:Object = my_array[i];
  6.     my_array[i] = my_array[nRandom];
  7.     my_array[nRandom] = temp;
  8.   }
  9. }

スクリプト001第2行目は、配列の長さ(Array.lengthプロパティ)を取得します。エレメントの数にもとづき、取出すランダムな位置のインデックス番号を計算するためです。

スクリプト001第3行目から、配列のエレメントすべてをwhileステートメントで繰返し処理します。ループの継続条件は、論理式でなく変数(i)になっています。変数が数値の場合、0以外の値はtrueと評価されます。変数(i)の値は、第4行目で1ずつ減算(ポストデクリメント)されます。したがって、変数値が0になるまで、エレメント数(配列の長さ)と同じ回数処理が行われることになります。

スクリプト001第4行目は、並べ替えるインデックス番号をランダムに定めます[*1]。得られる値は、0以上変数(i)値未満の整数です。変数(i)の初めの値は配列の長さで、配列の最後のエレメントのインデックスより1大きいです。したがって、0から最後のインデックス番号までの範囲の値がランダムに決まります。ただし、変数(i)の値は前述のとおり1ずつ減っていくので、繰返すたびに範囲は狭まります。

そして、スクリプト001第5〜7行目で、配列の最後からエレメントを遡って順に抜出し、ランダムに決められたインデックス番号のエレメントと差替えてゆきます。つぎのループでは変数(i)の値が1減って、ランダムなインデックスの範囲がひとつ狭まります。そのため、ひとたび差替えられたエレメントは、その位置を動くことがありません。後述するように、ここがポイントです。

[*1] 予め決めた範囲のランダムな整数を求める計算については、ActionScript 3.0で始めるオブジェクト指向スクリプティング 第33回「遠近法の投影」の「ステージにランダムなシェイプを配置する」をお読みください。


02 並べ替えの偏り
ランダムな処理で気をつけなければならないのは、偏りが出ないようにすることです。ところが、前掲スクリプト001をつぎのように書替えると偏りが生じます。

  1. var i:int = my_array.length;
    var n:int = i;   // 追加
    // while (i) {
  2. while (i--) {
  3.   // var nRandom:int = Math.floor(Math.random()*(i--));
      var nRandom:int = Math.floor(Math.random()*(n));

前掲スクリプト001との大きな違いは、配列の長さを別の変数(n)にとったことです(第2行目の後に追加)。そして、この変数にもとづいてランダムなインデックスを計算しています(第4行目)。すると、ランダムなインデックスの値の範囲は、いつもエレメントの初めから最後までとなり、ループの間ずっと変わりません。

学校の教室で席替えするときのくじ引きを例にして考えてみましょう。普通、教室の座席につけた番号のくじをつくり、ひとりひとり順に引いてその席に座ります。何の問題もありません。前掲スクリプト001は、これと同じ考え方にもとづきます。ひとたび席が決まれば、その位置を動かないのです。

けれど、後から書替えたスクリプトでは、引いたくじを毎回戻すのと同じです。もし、前の人がすでに座っている席番号を後の人が引いたら、席を入替えます。つまり、すべての人がすべての座席番号を引く可能性をもちます。逆に、最後のひとりがくじを引き終わるまで、誰も座席が確定しません。

それだけでしたら、煩わしいだけで済ますこともできるでしょう。けれど前述のとおり、この処理方法ではランダムな並べ替えに偏りが出かねません。簡単にするために、3人のくじ引きを考えます。

3人ですので、席は3つです。くじを毎回戻しますので、各自3つの席番号を引く可能性があります。すると、すべての場合を数え上げれば、27(= 3×3×3)とおりの引き方があります。しかし、3人の席の座り方は順列の6(= 3! = 3×2×1)とおりしかありません。27を6で割ると3余ります。つまり、3人の座り方(6とおり)のうち3つが他の3つよりも多く出ることになるのです[*2]

煩わしいうえに偏りが出るのはいただけません。そこで、毎回くじを戻すのではなく、ひとり引くたびにくじが減るのと同じように、ランダムな値の範囲もひとつずつ狭めなければならないのです。

[*2] 席番号の替わりに「a」「b」「c」の3つの文字を使って、27とおりのパターンを書出してみました。確かめてみると、半分のパターンが4回、残りの半分が5回発生しています。

No. 並べ替えのパターン
1 c a b
2 b c a
3 b a c
4 c b a
5 a c b
6 a b c
7 b c a
8 a b c
9 a c b
10 c b a
11 a c b
12 a b c
13 c a b
14 b c a
15 b a c
16 a c b
17 b a c
18 b c a
19 a c b
20 b a c
21 b c a
22 a b c
23 c a b
24 c b a
25 b a c
26 c b a
27 c a b


作成者: 野中文雄
作成日: 2011年10月11日


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