【Java初心者用】配列内の最大値と最小値を求める悪い例と良い例
プログラムの勉強をしていると、最大値や最小値を求めるプログラムを作成することがあります。どのようにして考えればよいのか、フローチャートや図解を使って説明します。
また、自分の作ったプログラムが果たして良いのか悩んでる方に向けて、どのようなプログラムが悪いものか、またどうすれば良いプログラムを作成することができるのか、それぞれの特徴を説明します。
皆さんが良いプログラムを書く時の参考にしてもらえば幸いです。
まず、この記事は以下のような人を対象としています。
対象者・最大値や最小値の求めるプログラムの作り方を知りたい人
・メソッドの分ける理由を知りたい人
・良いプログラムの作り方を知りたい人
この記事を読むと、次のようなことが理解できるようになります。
この記事を読むとできること・最大値や最小値を求めるプログラムの作り方を知ることができる
・悪いプログラムと良いプログラムの書き方を知ることができる
最大値と最小値を求めるプログラムを作ったのですが、これで良いのでしょうか?
結果が正しければ、そのプログラムは正解ですよ。
でも、初心者がやりがちな「良くない」プログラムですね。
「良くない」プログラムですか?
何が悪くて、どうすればいいのですか?
メソッドに分けると「良い」プログラムになりますよ!!
今回は、悪い例と良い例を使って解説してきます。
課題:最大値と最小値を求めるプログラムを作成する
今回、利用するプログラムは次のような仕様とします。
- 10個の数値の中の最大値と最小値を求める
- 数値は0から100の範囲の値とする
- 数値は乱数で求め、配列に保存する
- 10個の数値と最大値、最小値を最後に表示する
最大値と最小値を求めるアルゴリズム
配列に格納さている値の最大値と最小値を求めるプログラムのアルゴリズムをまず、考えてみましょう。
人間は複数の値を見ただけで、最大値と最小値を瞬時に求めることができます。しかし、コンピュータはもちろんそのようなことはできません。どのような手順を踏めば求められるのか、手取り足取り教えてあげる必要があります。その手順がプログラムとなります。
したがって、人間が瞬時に求めている処理を順番に、細かく分析し、それをプログラムとして記述しなければなりません。
一般的に、複数の値の中から最大値と最小値を求める方法は、1個ずつ値を取り出して、今覚えている最大値や最小値と比較し、新しい値の方が大きかったり小さかったりした場合に最大値や最小値を更新する、という手順となります。
テレビで放映されているカラオケの点数を競う方法と同じです。最初の挑戦者の点数がその時点の最高点となり、2人目の挑戦者の点数と現在の最高点を比較し、2人目の点数が高かったら最高得点者を入れ返る。この作業を最後の挑戦者まで行い、一番最後まで残っていた人が最高得点者となる。
処理の流れ(アルゴリズム)は以下の通りです。
- 配列の値を1つ取り出す
- 取り出した値と現在の最大値を比較する
- 取り出した値が現在の最大値より大きければ、最大値を取り出した値で上書き(更新)する
- 取り出した値と現在の最小値を比較する
- 取り出した値が現在の最小値より小さければ、最小値を取り出した値で上書き(更新)する
- この作業を配列の値がなくなるまで繰り返す
次に、このアルゴリズムをフローチャートにしてみます。
簡単な例で再度確認してみます。値は配列scoresに格納されている状態とします。説明を簡単にするために、今回利用する値は3個とします。その時の最大値を保存しておく変数をmax、最小値を保存しておく変数をminとします。
では、アルゴリズム/フローチャートに従って、それぞれの変数の値がどのように変化するのか、追跡してみます。
まず、最大値maxと最小値minの変数をそれぞれ「0」と「100」で初期化しておきます。次に配列のデータの個数分だけ、最大値と最小値を求める処理を繰り返します。この例では要素数は3個なので3回繰り返し処理を実行します。
1回目の処理では、配列の最初の値(添え字0)「52」が現在の最大値(0)と最小値(100)と比較して、大きいか小さいかを比較します。現在の最大値「0」よりも「52」の方が大きいので最大値の値を「52」で上書き(更新)します。一方、現在の最小値「100」よりも「52」の方が小さいので最小値の値を「52」で上書き(更新)します。
2回目の処理では、配列の2個目の値(添え字1)「34」が現在の最大値(52)と最小値(52)と比較して、大きいか小さいかを比較します。現在の最大値「52」よりも「34」の方が小さいので最大値の値は変わりません。一方、現在の最小値「52」よりも「34」の方が小さいので最小値の値を「34」で上書き(更新)します。
3回目の処理では、配列の3個目の値(添え字2)「78」が現在の最大値(52)と最小値(34)と比較して、大きいか小さいかを比較します。現在の最大値「52」よりも「78」の方が大きいので最大値の値を「78」で上書き(更新)します。一方、現在の最小値「34」よりも「78」の方が大きいので最小値の値は変わりません。
配列内の全ての値に対して上記のような処理をおこない、最終的に最大値と最小値に格納されている値が、求める最大値と最小値になります。この例では最大値は「78」、最小値は「34」となり、正しい結果になっていることが分かります。
悪い例のサンプルプログラム
では、初心者が一般的に作成する「悪い」例を示します。
プログラムの各行の説明をするためにコメントをあえて記述しています。しかし、各行の内容をそのままコメントにしているだけで冗長(無駄)なので、実際は記述しない方が良いでしょう。
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 |
package arrayMaxMin; import java.util.Random; public class MaxMinValueBad { public static void main(String[] args) { // 値を10個格納するための空の配列の用意 int[] a = new int[10]; int b=0; // 仮の最大値 int c=100; // 仮の最小値 // 乱数を作成するための準備 Random d = new Random(); // 10回繰り返す for(int i=0;i<10;i++) { // 0から100までの乱数を求め、配列に格納 a[i]=d.nextInt(101); // 作成した乱数が仮の最大値より大きいか if(a[i]>b) { // 大きい場合、最大値を更新 b=a[i]; } // 作成した乱数が仮の最小値より小さいか if(a[i]<c) { // 小さい場合、最小値を更新 c=a[i]; } } // 値を10個1行で表示 for(int i=0;i<10;i++) { System.out.printf("[%3d] ",a[i]); } System.out.println(""); // 改行 System.out.println("最大値 : "+b); // 最大値の表示 System.out.println("最小値 : "+c); // 最小値の表示 } } |
このプログラムの実行結果は以下の通りです。もちろん、正しい結果が表示されています。
悪いプログラムの特徴
プログラムの勉強を始めたばかりの初心者は、まず求められている結果が得られるプログラムを作成することが重要です。したがって、プログラムの内容がどうであれ、とりあえず目的を達成できれば問題ありません。しかし、勉強を続けていき、ある程度自分で考えたことがプログラムで作成できるようになったら、プログラムの書き方について注意を払う必要があります。
上のサンプルプログラムの悪い点は何でしょう? あなたの作成するプログラムでも同じような書き方をしていませんか? 当てはまる人は是非、これから気を付けてプログラムを作成してください。
以下が、このプログラムの悪い点です。
変数名が適当すぎて、何を意味しているのか把握できない
書籍でもたまに見かけますが、変数名を「a」「b」など英字1文字で定義している場合、すぐに訂正してください。このような変数名では、どのようなデータが格納されるのか把握することができません。書籍の場合、長い変数名を使うと紙面の幅に収まらず改行しなければならないため多少見づらくなります。それを防ぐために英字1文字の変数名を使っているはずです。
一般的に変数名は「キャメルケース記法」で定義します。なるべく変数名は英単語で表現しましょう。英語が不得意でローマ字で表現してしまう場合がありますが、ローマ字は人によって書き方が異なります。例えば、割り算の「商」をローマ字で表現すると、「sho」「syo」「shou」「syou」など色々な書き方があります。「sho」と記述しなければならないのに、自分の慣れたローマ字表現の「syo」と記述してしまい入力ミスになってしまう危険性があります。
プログラムの勉強をするためには、ある程度英語の勉強もしておいた方が良いでしょう。なお、インターネットにはプログラムで使える英単語の紹介をしているサイトもありますので、困ったときは参照してみて下さい。また、翻訳サイトを活用するのも良いでしょう。
インデントが適切にされておらず、見づらい
このサンプルプログラムは全くインデント(字下げ)がされていません。書き出し位置が左側で揃っているので、一見見やすいように見えますが、プログラムではインデントは非常に重要です。インデントが適切におこなわれていれば、if文などのブロック範囲が瞬時に把握することができます。
インデントをしないでも気にならない人は、もしかすると部屋でも整理整頓ができていないのでは? と心配してしまいます。
Eclipse(Pleiades)を利用している場合、[Ctrl]キーと[Shift]キーと[f]キーを同時に押すと、自動的にインデントなどの書式を設定(適用)してくれます。ぜひ、入力が一段落したら、このショートカットキー操作をおこなうようにして下さい。
main()メソッド内に全ての処理が記述されている
main()メソッドは書籍で言うと目次に当たります。目次を見れば、その書籍にどのような内容が記載されているのか把握できます。また、全体像の概要も理解できます。したがって、main()メソッド内には実行したい処理を全て記述しないように気を付けましょう。
書籍の見出しに該当するものが「メソッド」になります。main()メソッド内には、メソッドを中心にプログラムを記述していきます。なお、メソッド名も変数名と同様、理解しやすい名前をキャメルケース記法で記述します。
複数の処理がごちゃ混ぜで記述されている
プログラムを勉強中の初心者が一番やりがちなことが「違う処理を1つのまとまり(for文やif文)の中に詰め込んでしまう」ことです。
17行目から30行目までにfor文が記述されています。このfor文は「配列に乱数を1個ずつ格納する」ための処理です。しかし、乱数を格納するために記述しているfor文の中に、全く関係がない「最大値を求める処理」と「最小値を求める処理」が記述されています。初心者は別に大きな問題ではないと考えるかもしれませんが、基本的にプログラムは「1 fact in 1 place」(1か所には1つの処理のみを記述する)という考え方で記述をします。この考え方を意識していれば、メソッドに分割することが簡単におこなえます。
初期値に設定している値が危険
設定している初期値が危険とは、どのようなことでしょうか?
今回は最大値に「0」、最小値に「100」を設定していますが、初心者は「最大値なのに0? 100ではないの?」と疑問に思うかも知れません。または、最大値として「100」を設定してしまうかも知れません。実際に最大値に「100」を設定してプログラムを実行すれば分かりますが、「100」を設定すると配列にどんな値が入っていても結果として最大値は「100」となってしまいます。また、最小値に「0」を設定すると、配列の値に関係なく最小値の結果は「0」になります。このように初期値の設定を間違えると結果も間違えたものになってしまいます。
最大値として設定する値は実は一番小さな値にしなければなりません。同様に最小値として設定する値は一番大きな値になります。
初期値の設定は誤解されない値を適切に設定しなければなりません。
良い例のサンプルプログラム
では、次に良い例のプログラムのコードを示します。基本的に上で説明した「悪い点」を全て改善しています。
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 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 |
package arrayMaxMin; import java.util.Random; public class MaxMinValue { public static void main(String[] args) { // 配列に値を格納するメソッドを実行 int[] scores = getScores(); // 最大値を求めるメソッドを実行 int max = getMax(scores); // 最小値を求めるメソッドを実行 int min = getMin(scores); // 結果を表示するメソッドを実行 showResult(scores,max,min); } // 配列に値を格納するメソッドの定義 public static int[] getScores() { // 空の配列を作成 int[] array = new int[10]; // 乱数の準備 Random rand = new Random(); // 配列の要素数分、繰り返す for(int i=0;i<array.length;i++) { // 求めた乱数を配列に格納 array[i]=rand.nextInt(101); } // 値が格納された配列を返す return array; } // 値が格納された配列を受け取り、最大値を求めるメソッドの定義 public static int getMax(int[] scores) { // 配列内の最初の値を最大値と仮定する int max = scores[0]; // 2個目から最後まで繰り返す for(int i=1;i<scores.length;i++) { // 配列の値が仮の最大値より大きいか if(scores[i]>max) { // 大きい場合、最大値を更新 max=scores[i]; } } // 最終的に求めた最大値を返す return max; } // 値が格納された配列を受け取り、最小値を求めるメソッドの定義 public static int getMin(int[] scores) { // 配列内の最初の値を最小値と仮定する int min = scores[0]; // 2個目から最後まで繰り返す for(int i=1;i<scores.length;i++) { // 配列の値が仮の最小値より小さいか if(scores[i]<min) { // 小さい場合、最小値を更新 min=scores[i]; } } // 最終的に求めた最小値を返す return min; } // 配列と最大値、最小値を受け取り結果を表示するメソッドの定義 public static void showResult(int[] scores,int max,int min) { // 配列に値が存在する間繰り返し値を変数に取り出す for(int score : scores) { // 1行にまとめて配列の値を順番に表示 System.out.printf("[%3d] ",score); } System.out.println(""); // 改行 System.out.println("最大値 : "+max); // 最大値を表示 System.out.println("最小値 : "+min); // 最小値を表示 } } |
良いプログラムの特徴
悪いプログラムの改善点を順番に説明します。
格納している情報が分かるような変数名を利用している
点数を格納する配列名は「scores」、最大値は「max」、最小値は「min」と内容が把握しやすい変数名を付けています。一般的に配列には複数の値が保存されるため、配列名は複数形が良いとされています。「score」だと1つの点数、「scores」なら複数の点数と容易に想像することができます。
また、最大値や最小値を求める処理はメソッドに分けていますが、メソッド名も処理内容が推測できるような名前を付けています。値を配列に格納する「getScores()」、最大値を求める「getMax()」、最小値を求める「getMin()」とキャメルケース記法でそれぞれ表記しています。
適切にインデントが設定されている
一目瞭然ですが、適切にインデントを設定しています。このようにインデントを設定すると、for文やif文のブロック(範囲)が明確になり、プログラムが見やすいくなります。
Eclipseを利用している場合、プログラムを入力後に改行すると自動的にインデントが設定さます。したがって、普通に入力していればインデントがずれることはありません。コピペなどをした時にずれてしまうことがあります。
ずれてしまった場合、1行ずつ[Tab]キーを押してインデントを設定するのは面倒です。複数行をドラッグ等で選択し、[Tab]キーを押すとまとめてインデントが設定されて楽です。また、[Ctrl]+[Shift]+[f]キーを同時に押すと一括でインデントが設定されます。なお、このショートカットキーを利用すると「=」の左右に自動的に空白が追加され、より見やすく整形されます。
また、インデントされた位置を左に戻したい場合は、[Shift]+[Tab]キーを同時に押してください。[BackSpace]キーを何度も押して文字を削除しながら、カーソルを左に動かすよりも作業は楽になります。
main()メソッド内にはおこなう処理(メソッド)だけが記述されている
悪いプログラムとの大きな違いは、main()メソッド内の記述内容となります。良いプログラムの場合、main()メソッド内には実行する他のメソッドだけを簡潔に記述します。
この結果、このプログラムではどのような処理がどのような順番で実行されるのか概要を容易に把握することができます。それぞれの詳細処理については別途メソッドを参照すれば確認することができます。
また、メソッド名の処理内容が把握できるような名前を付けているため、コメントが無くてもそのメソッドでどんな処理をおこなうのか予想することができます。
メソッドでは1つの処理しか行っていない
悪いプログラムでは配列にデータを格納しながら最大値と最小値も同時に求めていました。しかし、良いプログラムでは配列にデータを格納するためだけのメソッド、最大値だけを求めるメソッド、最小値だけを求めるメソッドと言うように1つのメソッドには1つの処理内容しか記述していません。
このように1つのメソッドには、特定の処理だけをおこなうプログラムを記述します。
1つの処理の中でまとめて色々な処理をおこなった方が効率が良いように思えますが、それぞれ分割して記述した方が何かと利点が多くなります。
例えば、最大値の処理内容が変更になったのでプログラムの修正をおこなう場合、まとめて記述しているとそのプログラムの中でどの部分が最大値を求める処理なのか分かりづらいことがあります。最大値を求めるためのプログラムだと判断し修正したら、実は全く関係ないプログラムを修正していしまい、結果的に処理がうまく実行できなくなってしまった、ということが起こる危険性があります。
しかし、処理ごとにメソッドに分けていれば、最大値を求める処理は最大値を求めるメソッドだけに記述されているので、間違えて他の処理部分を書き換えてしまう心配がありません。
さらに、メソッドは独立して記述されているため簡単に取り換えが可能です。今回、メソッド「showResult()」はディスプレイに結果を表示していますが、例えば結果をファイルに保存したい場合、ファイルにデータを保存するメソッドを別途定義し、それをmain()メソッド内で呼出すように修正します。このようにメソッドを独立(分離)して定義している場合、既存のプログラムを大幅に変更することなく、用途に合わせてメソッドを使い分けることができます。
誤解されない初期値が設定されている
悪いプログラムでは最大値と最小値の初期値をそれぞれ「0」と「100」に設定していました。しかし、既に説明していますが初期値の設定を間違えると正しい結果が得られません。このようにプログラマが勝手に自分の想像で初期値を設定してしまうと問題が発生する危険性があるので、なるべく間違えが起こらないような初期値の設定が求められます。
良いプログラムでは最大値と最小値の初期値を配列の1つ目の要素に格納されている値を初期値として設定しています。このように実際に格納されている値を初期値として利用すると、確実に正しい処理がおこなえるようになります。
初期値を設定する場合、どのような値が最適なのかをじっくり考えましょう。
なお、配列の内の最初の値を初期値として利用しているので、最大値と最小値を求める繰り返し処理のカウンタ変数は「0」からではなく「1」から始まります(40行目と56行目)。
また、for文の繰り返し処理で配列を対象とすると場合、終了条件には要素数「10」を直接記述せず、「配列名.length」と記述しましょう(27行目、40行目、56行目)。配列の要素数はもしかするといずれ変更される可能性があります。要素数を直接数字で記述しておくと後で修正しなければなりません。しかし、プログラムの行数が多くなると、どこを変更すれば良いのか探すのが大変になり、修正し忘れる危険性があります。要素数の修正を忘れてもコンパイルエラーや例外が発生しませんが、正しい結果は得られません。
例えば要素数を10個から100個に変更してプログラムを実行した場合、for文の終了条件が10のままだと10個のデータだけの最大値と最小値が求められるだけで、エラーにはなりません。しかし、これではもちろん正しい結果とは言えません。
このようなヒューマンエラーを防ぐためにも配列の要素数を使う場合は、直接要素数を数字で記述せず、「lengthプロパティ」を利用するようにしてください。
良いプログラムの定義
ここまで、悪いプログラムと良いプログラムの特徴を説明してきまし。では、一般的に良いプログラムとはどんなプログラムででしょうか?
一言で言うと、「可読性が高い」「メンテナンス性が高い」プログラムが良いプログラムとなります。
可読性が高いプログラム
可読性が高いプログラムとは、「読みやすい」「理解しやすい」プログラムとなります。
- 変数名、配列名、メソッド名から内容が推測しやすい
- 命名規則がキャメルケース記法などで統一されている
- main()メソッド内のメソッドからプログラム全体が把握しやすい
- インデントが適切に設定さてている
メンテナンス性が高いプログラム
メンテナンス性が高いプログラムとは、「処理がメソッドごとに分かれている」プログラムとなります。
- 1つのメソッドには1つの処理のみ定義されている
- 変数名、配列名、メソッド名から内容が推測しやすい
まとめ
今回は、最大値と最小値を求めるプログラムの書き方と、良いプログラムの特徴について解説しました。
プログラムは自分だけが見るものではありません。他のプログラマも見ることがあります。そのため他のプログラマが見た時に容易に内容を把握でき、必要個所を適切に修正できるよう、見やすい記述を心掛ける必要があります。
初めのうちは「動くプログラムを書く」ことに重点を置いて勉強していきますが、その後は「他のプログラマにも見やすいプログラムを書く」ことに注意してください。
・最大値は仮の最大値と配列の値を比較し、配列の値が大きければ最大値を更新して求める
・最小値は仮の最小値と配列の値を比較し、配列の値が小さければ最小値を更新して求める
・main()メソッド内にはメソッド名のみを記述し、全体像を把握できるようにする
・メソッドには関係ない処理はあまり書かない
・プログラムは他のプログラマが理解しやすいように思いやりを持って記述する
まずは、最大値と最小値の求め方は理解できましたか?
図を使って説明してもらえたので、理解できました!!
良いプログラムの特徴についてはいかがでしたか?
行数が短い方が良いプログラムだと思っていたので、1つの処理の中で色々なことを記述していました。それが逆効果だと聞いて、ちょっとびっくりしています。
もちろん、無駄のない短いプログラムを書くことは重要ですよ。
でも、メソッドなどに分けて行数が長くなっても、問題はありません。
他のプログラマが読みやすい記述をぜひ心掛けてください!!