ボールを順番に取り出す問題を通してシャッフルについて考察してみよう。
お題
今回扱うお題は以下
袋の中に5つのボールが入っている。このボールには1から5の番号がついている。ちょうどビリヤードのボールが袋に5個入っていることを想像してもらえればよい。この袋から一つずつボールを取り出したととする。その時のボール番号を順番に表示する処理を作成せよ。(取り出したボールは袋に戻さない)
[実行例](実行するたびに変わる)
[3,5,2,4,1]
ポイント
ランダムに取り出すことになりそうだが、同じボールを取り出すことはできない。
この部分をどう考えるかがポイントになりそうだ。
色々やり方はありそう。。。
まずはいろいろなやり方を考えてみよう。
番号を抽選して、過去に出ていたらやり直す
出た目を配列に入れていって、もし過去に出ていた番号だったら抽選し直すという方法だ。
import java.util.*;
public class Sushi{
public static void main(String[] args){
//5個分のボール配列
int[] balls=new int[5];//最初は全部0
//ボールの数だけ回す
for(int i=0;i<balls.length;i++){
int num;
int j;
do{
//乱数を生成
num = new Random().nextInt(5)+1;
//過去に同じ値がないか調査
for(j=0;j<i;j++){
//もしあったら
if(balls[j]==num){
//ループを途中で抜ける
break;
}
}
}while(j < i);//ループを途中で抜けていたら乱数生成からやり直し
balls[i]=num;//ここに到達したらその数は新登場なので配列に入れる
}
System.out.println(Arrays.toString(balls));
}
}
なるほど、これならば確かに実現できる。ただ、ボールが1000個になった場合、最後の方になるとダブってばかりで新しい目を抽選するのがとても大変そうだ。
複数回交換してみる
最初に1から5の配列を用意してランダムに2値を入れ替えるという方法も考えられる。
import java.util.Random;
public class Test {
public static void main(String[] args){
int[] balls = {1,2,3,4,5};
for(int i = 0;i< balls.length*2;i++){
int id1=new Random().nextInt(balls.length);
int id2=new Random().nextInt(balls.length);
if(id1 != id2){
int tmp=balls[id1];
balls[id1]=balls[id2];
balls[id2]=tmp;
}
}
System.out.println(Arrays.toString(balls));
}
}
これならばボールの数が増えても問題がなさそうだ。ただ、今回は適当にボールの数の2倍回数分ボールの入れ替え(swap)を行ってみたがこれは適当な回数なのだろうか?
解答例
では正解となる考え方を順番に見ていこう。
まず、最初の1つ目だがこれは5個のボールからランダムに選べば良い。
仮に3が選ばれたとしよう
一番先頭の値(1)と交換する
このような状態になった
3番はもう引くことができない。次は残りの4つから一つをランダム抽選する。
5番が選ばれた
今度は先頭から2つ目のボールと交換する
このような状態になる
3番と5番のボールは引けないので残りの3つから抽選する
2番が選ばれた。
先頭から3番目のボールと交換する
このような状態になった。
最後に残り2つから抽選する
4番が選ばれた
交換の必要はない
最後の一つは抽選する必要がない。
今回の結果は
3,5,2,4,1
だったようだ。
上でやったことをコードで書いてみる
import java.util.*;
public class Test {
public static void main(String[] args) {
int[] balls ={1,2,3,4,5};
//ボールの抽選回数はボールの数-1(最後の1個は抽選しなくてよい)
for(int i=0;i<balls.length-1;i++){
//選ばれるボールのindex,一つ選ぶたびに抽選範囲が一つずつ狭まる
//最初0-4(全部)
//次1-4(最初をのぞく残り)
//次2-4
//次3か4
int index = new java.util.Random().nextInt(balls.length-i)+i;
int tmp = balls[index];//選ばれたボールの番号
balls[index] = balls[i];//最初の要素とswap
balls[i] = tmp;
}
System.out.println(Arrays.toString(balls));
}
}
コメント部分を除去したコードが以下
import java.util.*;
public class Test {
public static void main(String[] args) {
int[] balls ={1,2,3,4,5};
for(int i=0;i<balls.length-1;i++){
int index = new java.util.Random().nextInt(balls.length-i)+i;
int tmp = balls[index];
balls[index] = balls[i];
balls[i] = tmp;
}
System.out.println(Arrays.toString(balls));
}
}
これがボールを袋からランダムに取り出すという行為を確率も含め忠実に再現したコードとなる、これは結果として配列をシャッフルするアルゴリズムとなっている。
大事なこと
今回はボールを袋から取り出すお題をテーマに配列をシャッフルするアルゴリズムについて学習した。大事なことは上のソースコードを覚えることではなく、上で詳細に行った考え方をしっかりと理解することだ。こういった考え方を一つ一つ理解していくことでプログラミングのセンスが磨かれていく。
コメント