【Java初心者用】配列に重複しないように値を格納する方法
配列内に重複しないように値を格納するためには、どのようなプログラムを作成すれば良いでしょうか?
例えば、1から10までの乱数を作成し配列に5つ値を保存したいとします。乱数なので実行するまでどんな値が作成されるか分かりません。ただ単純に作成した乱数を配列に保存してしまうと、既に同じ値を保存している可能性があります。配列に格納する際に既に同じ値が保存されているかどうかのチェックをしなければなりません。
今回は、重複しないように値を保存するプログラムの一例を解説します。
まず、この記事は以下のような人を対象としています。
対象者・重複しないように値を配列に格納する方法を知りたい人
・プログラムを実行した時の値の確認方法を知りたい人
・アルゴリズムやフローチャートの考え方を知りたい人
この記事を読むと、次のようなことが理解できるようになります。
この記事を読むとできること・重複しないように値を配列に格納するプログラムを知ることができる
・アルゴリズムやフローチャートの考え方を知ることができる
配列に重複しないように値を保存したいのですが、どうすれば良いですか?
保存する前にif文などで重複を確認する必要があります。
if文でのチェックですか? 比較的簡単そうですね。
実はそうでもないんですよ!!
実際にプログラムを使って解説していきます。
関連する記事として以下の記事もぜひご覧ください。
プログラムで実現したいこと(仕様)
以下の仕様を満たすプログラムを今回作成します。
- 配列の要素数は5つ
- 格納するデータは0から10の整数
- 整数は乱数で求める
- 重複するデータは格納できない
重複を考慮しない場合のプログラム
重複しているかどうかをチェックせず、単純に乱数を5回作成し配列に保存するプログラムは以下の通りとなります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
package myArray; import java.util.Random; public class ValueArray { public static void main(String[] args) { // 要素数5の配列を作成 int[] array = new int[5]; // 乱数の準備 Random rand = new Random(); // 配列の要素数分繰り返す for(int i=0 ; i<array.length;i++) { // 0から10のうちの乱数を作成し配列に格納 array[i] = rand.nextInt(11); } // 配列内の値を順番に表示 for(int value : array) { System.out.print(value+" "); } } } |
コードの解説
9行目
整数の値を5個格納できる配列を作成しています。宣言のみおこなっているので、各要素には「0」が初期値として自動的に格納されています。
12行目
乱数を利用するためにRandomクラスのインスタンスを生成し、変数randで参照できるようにしています。
15行目
値を5個配列に格納すれば良いので、値の個数分(5回)繰り返し処理を実行します。for文の変数iの値は配列の添え字として利用します。
17行目
作成した乱数(0から10の内のどれか)を配列の指定した添え字の要素に格納しています。
21行目から23行目
配列内のデータを拡張for文を使って1個ずつ取り出し、出力しています。なお、値が繋がらないように空白を1つ追加しています。
実行結果
一見すると、上記のプログラムでも問題ないように見えます。実行すると仕様通り重複しない結果が表示される場合があります。しかし、何度も繰り返し実行すると重複するデータが格納されていることが確認でき、このプログラムは仕様を満たしていないことが分かります。
重複を考量した場合のプログラム
上のプログラムの問題点はデータを格納する回数を5回と限定している点です。配列の要素数が5つなので格納するデータの個数も5個です。したがって、for文を5回繰り返し実行すれば良いように思えますが、この考え方(アルゴリズム)が間違えています。
実際は5個のデータを格納する処理は5回とは限りません。もしかすると、同じデータが10回連続して作成される可能性もあります。このように何回繰り返すか分からない場合に利用するのはfor文ではなくwhile文となります。while文は「指定した条件が成り立つ間繰り返す」という処理をおこないます。つまり条件が成り立っていれば何回でも実行されるので、実行する回数は未定です。
また、格納するデータを乱数で作成したら、そのデータが配列に既に格納されているかチェックしなければなりません。この処理は配列から1個ずつデータを取り出し、乱数と一致するかどうかをチェックします。このチェックは配列内の全てのデータに対して行います。したがって、この処理は繰り返し処理(for文)となります。
したがって、while文の中に別のfor文が含まれる繰り返し処理のネスト(入れ子)構造となります。
重複を考量したプログラムのアルゴリズムは次のようなものが考えられます。まずは、文章で記述してみます。
- 中身が空、要素数が5の配列を準備する
- 格納したデータの個数をまず「0」とする
- 格納したデータの個数が5未満の間、以下の処理を繰り返す。個数が5になったら処理9に飛ぶ
- 乱数を1つ作成する
- 配列内のデータを1個ずつ取り出し、乱数と一致するか比較する
- 一致しない場合、重複していないので配列内に乱数を格納する
- 乱数を格納したのでデータの個数を1加算する
- 処理3に戻る
- 結果を表示する
フローチャート
このアルゴリズムをフローチャートにすると以下のようになります。なお、各記号には説明しやすいように番号を意図的に付けています。
フローチャートの書き方については以下の記事を参考にして下さい。
アルゴリズムの考え方は以下の記事で説明しています。
フローチャートの解説
①.フラグ=true
このフラグは作成した乱数が配列内の値と重複しているかどうかをチェックするために利用します。重複していない場合は「true」が格納されるようにします。
②.個数=0
このプログラムでは重複しない値を5個配列に格納するまで不特定回数処理を実行します。したがって、終了するためには何個配列に値を格納したのか数えなければなりません。この個数の変数は配列に格納できた値の個数を格納します。まだ、1つも格納していないので初期値は0です。
③.個数<5
配列に格納された値の個数が5未満の間、処理を繰り返します。個数の値が0から始まっているので、「5未満」が条件となります。イコールを付けて「5以下」と書いてしまうと、計6個値が格納されてしまうので気を付けましょう。
④.乱数を1つ作成
③の条件が成り立つということは、まだ5個配列に値を格納していないので、格納する値の候補を乱数で1つ作成します。
⑤.ループ i:0,1,・・,個数
作成した乱数の値が既に配列に格納されているかチェックします。この処理は配列に格納されている値全てを対象に繰り返し処理で行います。配列から最後の要素まで値を1個ずつ取り出して処理をおこなうので、繰り返し処理となります。
⑥.要素iの値==乱数
配列から添え字iに該当する値を1つ取り出し、格納しようとしている乱数の値を等しいかチェックします。等しい場合は重複していることなります。
⑦.フラグ=false
重複している場合、フラグにfalseを設定します。
⑧.ループの中断
重複している場合、配列にまだ値が格納されていても⑥の処理は行う必要がありません。重複してい乱数は配列に格納しないため、重複していることが分かったら、すぐに重複をチェックする繰り返し処理を中断します。残りの値を1個取り出して一致するかチェックしても構いませんが、余計な処理が多くなるだけです。
⑨.フラグ=true
配列から取り出した値と格納しようとしている乱数が等しくなければ、とりあえず重複していないので、フラグにtrueを設定します。ただし、重複していないのは今配列から取り出した値だけなので、残りの配列内の値に対しても同様のチェックが必要です。したがって、フラグにtrueを設定したら、カウンタ変数iの値を1加算して次の繰り返し処理をおこないます。
⑩.フラグ==true
配列に格納されている全ての値と乱数の値が一致するか調べる繰り返し処理が終了しているので、重複するかどうかの最終的な結果がフラグに格納されています。フラグの値がtrueかどうかをチェックし、重複していたかどうか判定します。
⑪.要素[個数]に乱数を格納
もし、重複していない場合は求めら乱数を配列内に格納します。ここで、注意点は配列の添え字は個数用の変数となる点です。
⑫.個数を1加算
配列内に乱数を格納したので、格納した個数(変数)を1加算します。
なお、フラグがtrueでない(false)場合、乱数は配列に格納せず、また格納した値の個数を数える変数も変更せず、外側の繰り返し処理をやり直します。
⑬.結果を表示
最終的に乱数を5個配列に格納すると繰り返し処理が終了するので、配列内のデータを表示します。
プログラムのコード
また、このフローチャートを元に作成したプログラムは以下の通りです。ぜひ、フローチャートと比較してみて下さい。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
package myArray; import java.util.Random; public class UniqueValue { public static void main(String[] args) { // 乱数の準備 Random rand = new Random(); // 重複するかどうかの変数(true:重複しない) boolean isUnique = true; // 中身が空の配列(要素数5)を作成 int[] array = new int[5]; // 格納した個数の初期化 int count = 0; // 個数が5未満の間繰り返す while (count < 5) { // 乱数(0~10)を1つ作成 int value = rand.nextInt(11); // 作成した乱数が配列内にすでにあるかのチェック for (int i = 0; i < count; i++) { // 配列内の値と乱数が等しい(重複している)かどうか if (array[i] == value) { isUnique = false; // 重複していればfalse break; // 残りのチェックを飛ばす } else { isUnique = true; // 重複していなければtrue } } // もし、重複していない場合 if (isUnique == true) { // 配列に乱数を格納 array[count] = value; // 個数を1加算 count++; } } // 配列内の値を順番に表示 for (int num : array) { System.out.print(num + " "); } } } |
プログラムの追跡(詳細説明)
このプログラムの処理を順番に実行しながら、変数や配列の値を確認していきます。
17行目まで実行した状態
各変数、配列の初期化処理がおこなわれる。なお、変数valueと変数iはこの時点ではまだ存在していませんが、説明のために記載しています。
20行目 while(count<5)のチェック 【1週目】
変数countの値は「0」で(count<5)をチェックすると結果は「true」なので、21行目以降を処理する。
23行目 乱数の作成
例えば、乱数として「5」が作成され、変数valueに「5」が格納される。
26行目 for(int i=0;i<count;i++)の実行 【1週目】
変数iの初期化によって、変数iには「0」が格納される。次に終了条件(i<count)のチェック。「i(0)<count(0)」の条件は成り立たないのでfor文の{}処理は行わない。36行目以降を処理する。
38行目 if(isUnique==true)のチェック
変数isUniqueの値は「true」なので、この条件が成り立つ。39行目以降を処理する。
42行目 array[count]=value
変数countの値は「0」なのでarray[0]に変数valueの値の「5」を格納する。なお、変数iはfor文内のローカル変数なのでこの時点では存在しない。
44行目 count++;
変数countには「0」が格納されていたので、+1した結果の「1」が格納される。
次のwhile文の処理をおこなうため20行目に戻る。
次に作成される乱数が「3」と仮定し、どのように処理されるのか確認してみます。
1週目のwhile文が終わった時点の状態
変数iはfor文内のローカル変数なので、この時点では存在していない
20行目 while(count<5)のチェック【2週目】
変数countの値は「1」で(count<5)をチェックすると結果は「true」なので、21行目以降を処理する。
23行目 乱数の作成
乱数「3」が作成され、変数valueに「3」が格納される。
26行目 for(int i=0;i<count;i++)の実行 【1週目】
変数iの初期化によって、変数iには「0」が格納される。次に終了条件(i<count)のチェック。「i(0)<count(1)」の条件は成り立つので、29行目以降を処理する。
29行目 if(array[i]==value)のチェック
変数iは「0」なのでarray[i]は「5」。変数valueの値は「3」。「5==3」の条件は成り立たないので32行目以降を処理する。
33行目 isUnique==trueの実行
変数isUniqueに「true」が代入され、for文の処理が1回終了するので、変数iが1加算され、26行目に戻る(次のfor文)
26行目 for(int i=0;i<count;i++)の実行 【2周目】
変数iには「1」が格納されている。次に終了条件(i<count)のチェック。i(1)<count(1)の条件は成り立たないので、38行目以降を処理する
38行目 if(isUnique==true)のチェック
変数isUniqueの値は「true」なので、この条件が成り立つ。39行目以降を処理する。
42行目 array[count]=value
変数countの値は「1」なのでarray[1]に、変数valueの値の「3」を格納する。なお、変数iはfor文内のローカル変数。for文が終了したので存在しない。
44行目 count++;
変数countには「1」が格納されていたので、+1した結果の「2」が格納される。
次のwhile文の処理をおこなうため20行目に戻る。
このように重複しない値の場合、配列に値が保存されます。
次に作成される乱数を「5」とします。この値は配列には格納されないので、その場合の処理内容についても確認してみます。
1週目のwhile文が終わった時点の状態
変数iはfor文内のローカル変数なので、この時点では存在していない。
20行目 while(count<5)のチェック【3週目】
変数countの値は「2」で(count<5)をチェックすると結果は「true」なので、21行目以降を処理する。
23行目 乱数の作成
乱数「5」が作成され、変数valueに「5」が格納される。
26行目 for(int i=0;i<count;i++)の実行
変数iの初期化によって、変数iには「0」が格納される。次に終了条件(i<count)のチェック。「i(0)<count(2)」の条件は成り立つので、29行目以降を処理する。
29行目 if(array[i]==value)のチェック
変数iは「0」なのでarray[i]は「5」。変数valueの値は「5」。「5==5」の条件は成り立つので30行目以降を処理する。
30行目 isUnique = false; の実行
変数「isUnique」に「false」が格納される。
38行目 if(isUnique==true)のチェック
変数isUniqueの値は「false」なので、この条件が成り立たない。47行目以降を処理する。つまり20行目に戻り、4週目のwhile文を実行する。
次のwhile文の処理をおこなうため20行目に戻る。
このように、重複する値の場合は配列に値を代入せず、代入した値の個数も増加せずに、次の繰り返し処理(while文)を実行する。
まだ処理の途中ですが、プログラムの追跡はここまでとします。
このように初心者の人はプログラムが実行されると、変数や配列の値が具体的にどのように変化するのかプログラムを1行ずつ順番にチェックすると良いでしょう。なお、Eclipseなどの統合開発環境ソフトには「デバッグ」という機能が付いています。これは今回説明したように実行途中の変数などの値が確認できる機能です。自分の考えた通りにプログラムが動かない場合などに利用すると、間違えているコードなどを探すことができるので、是非使用してみて下さい。
まとめ
今回は、配列に重複しないように乱数の値を格納するプログラムについて説明しました。格納する個数が決まっているのでfor文を使えば良いように思えますが、実際には何回処理されるか回数がわかりません。したがって、while文を使う必要があります。
プログラムを勉強中の初心者は色々な処理をおこなうプログラムをサンプルとして確認しておいた方が良いでしょう。自分でゼロから作れなくても構いません。見たことがないプログラムは自分ではなかなか作れません。とにかく、たくさんのプログラムに触れるようにしましょう!!
・格納する値が決まっているからと言って、for文を使えば良いわけではない。どのような処理がおこなわれるのか、考える必要がある
・重複するかどうかは、配列内の値を1つずつ取り出して、一致するかどうかをif文でチェックする
・プログラム実行中の変数の値を自分で追跡できるようなスキルがあると良い
今回のサンプルプログラムはいかがでしか?
while文の中にfor文があるのでかなり複雑でした。
アルゴリズムやフローチャートを考える練習問題としても良い題材ですよ!!
自分でゼロから作れと言われたら、厳しい気がします。
今回はちょっと原始的な方法を使いましたが、実はJavaでももっと簡単に重複するかどうかをチェックできる方法あるんですよ。
それについては、いずれ説明します。