配列の平均

生田 陸人
LuaGate エンジニア / 現役エンジニア
編集 LuaGate編集部

配列の平均 とは

配列の要素を全部足して、要素数で割って平均を求める。空配列のときの 0 除算を避けることと、整数除算ではなく小数として計算するキャストの考え方を身につけよう。

配列の平均を求める

前のレッスンで配列の 合計for ループでぐるっと足し上げる練習をしました。今回はその一歩先、配列の平均 を求めるテーマに踏み込みます。アルゴリズムの世界では「平均」「合計」「最大」「最小」「個数」はセットで覚えられる基本中の基本で、Java でも Python でも JavaScript でも同じ発想で書けます。ここでパターンをしっかり押さえておくと、これから先のあらゆる集計処理が一気に楽になります。

平均の定義はとてもシンプルです。合計を個数で割る、これだけ。算数の授業で 100 回くらい見たやつですね。avg = sum / count の式を、配列に対してそのまま当てはめれば良いだけです。ただし Java でこれをコードにすると、入門者がほぼ全員引っかかるワナが 2 つ用意されています。整数除算0 除算 です。

平均はビジネスの世界でもっとも使われる統計量のひとつです。アンケートの満足度、テストの点数、商品の評価、サーバーのレスポンスタイム、本当にあらゆる場面で「平均値」が登場します。今回扱う arrayAvg のロジックは、現場の Java バックエンドでも形を変えて何度も書くことになる定番処理です。

平均 = 合計 ÷ 個数

手で計算する流れを言葉にすると、次のとおりです。

  • 配列の要素を全部足して 合計 を出す
  • 配列の長さ (要素の 個数) を取る
  • 合計 ÷ 個数 を計算して 平均 を返す

Java のコードに落とすと、こんな構造になります。

Java

public class Solution { public static double arrayAvg(int[] arr) { int sum = 0; for (int i = 0; i < arr.length; i++) { sum = sum + arr[i]; } return (double) sum / arr.length; } }

わずか 7 行ですが、ここに今回のレッスンで覚えてほしいエッセンスが全部詰まっています。int sum = 0; で合計のいれものを用意して、forarr.length 回ループしながら arr[i] を足し込み、最後に (double) sum / arr.length で割り算をしています。

arr.length は配列の長さを返すプロパティで、メソッドではないので () を付けません。String の長さは s.length() (メソッド) と書くのに、配列は arr.length (プロパティ) と書く、という地味な違いがあります。文法的なお作法なのでそういうものとして覚えてしまうのが早いです。

整数除算という落とし穴

上のコードでわざわざ (double) sum / arr.length と書いている理由を説明します。これが今回いちばん大事なポイントです。

Java では int / int の計算結果は int になります。これを 整数除算 と呼びます。たとえば 5 / 22.5 ではなく、小数点以下を切り捨てた 2 になります。7 / 321 / 40 です。Python 経験者にとっては衝撃の挙動かもしれませんが、Java C C++ Go などの伝統的な静的型言語では、この方式が標準です。

Java

int a = 5; int b = 2; double result = a / b; // 結果は 2.0 (整数除算の結果 2 を double にしただけ) double correct = (double) a / b; // 結果は 2.5 (片方を double に昇格させてから割り算)

double result = a / b; は一見正しそうに見えますが、右辺の a / bint 同士の割り算 なので先に 2 が計算され、それを double に代入しているだけです。代入のときに double に変換しても、もう小数点以下の情報は失われています。

対して (double) a / b は、a を先に doubleキャスト (型変換) してから割り算をしています。片方が double になると、Java のルールでもう一方も自動的に double に昇格されて、結果も double になります。これでようやく 2.5 が手に入ります。

キャストの位置がとても大事です。(double)(a / b) と書くと、先に a / bint 同士で計算されて 2 になり、その結果を double にキャストして 2.0 になります。これでは意味がありません。割り算の前に、片方の値を double にしておく のがコツです。

今回の平均計算でも、return (double) sum / arr.length;(double)sum の直前にあるおかげで、sum / arr.length が小数として計算されます。順番を間違えて return (double)(sum / arr.length); と書くと、arrayAvg(new int[]{4, 6}) の答えが 5.0 ではなく 5.0 でラッキー、arrayAvg(new int[]{4, 7}) の答えが 5.5 ではなく 5.0 のように、こっそり間違った値が出てしまいます。

0 除算を避ける

もうひとつのワナが、空配列 の扱いです。arr.length0 の状態で sum / arr.length を計算するとどうなるでしょうか。

答えは「実行時例外」です。具体的には ArithmeticException: / by zero が飛んできて、プログラムが止まります。int の世界ではこれは Java の確定動作ですが、double の世界ではちょっと違って NaN (Not a Number) という特別な値になります。どちらにせよ「平均は計算できない」状態ですね。

現場のコードでこれが起きると、深夜にアラートが鳴って起こされます。なので 割り算の前に、必ず分母が 0 でないかチェックする のが Java エンジニアの常識です。今回の課題でも「空配列なら 0.0 を返す」と仕様で決まっているので、ループの前に if でガードしてあげましょう。

Java

if (arr.length == 0) { return 0.0; }

この 3 行を for の前に置くだけで、空配列のときに早めに return してくれるようになります。「異常な入力は早めに弾く」というスタイルは ガード節 (Guard Clause) と呼ばれ、可読性が高くてバグも減るので、入門のうちから手癖にしておきたいテクニックです。

どんな値を返すべきかは、仕様によります。今回は 0.0 を返す約束ですが、現場のコードでは NaN を返したり、Optional<Double> で包んだり、IllegalArgumentException を投げたりと、流儀がいくつかあります。「空のとき何を返すか」は、メソッドを設計するときに必ず考えるべきテーマです。

動きを追ってみる

ここまでの考え方を 1 つの図にまとめます。配列を受け取ってから平均を返すまでの流れを順番に見ていきましょう。

diagram (will load when visible)

最初にやるのは「空かどうか」のチェック、次に合計を for で作って、最後に (double) キャストを挟んで割り算、という三段構えになっています。この形が頭に入っていれば、arrayAvg(new int[]{4, 6}) でも arrayAvg(new int[]{10, 20, 30, 40}) でも arrayAvg(new int[]{}) でも、迷わず正解にたどり着けます。

試しに arrayAvg(new int[]{2, 2, 2}) の動きを手で追ってみましょう。arr.length3 なので空配列チェックは通過、sum0 から始まり、i = 0sum = 2i = 1sum = 4i = 2sum = 6 まで増えます。最後に (double) 6 / 32.0 が計算されて返ります。手で追える Java のシンプルさが伝わってきますね。

拡張版 ─ 拡張 for 文で書く

ここまで for (int i = 0; ...)インデックス付き for で書きましたが、合計を作るだけなら Java拡張 for 文 (for-each) を使うともっとすっきり書けます。

Java

public class Solution { public static double arrayAvg(int[] arr) { if (arr.length == 0) { return 0.0; } int sum = 0; for (int value : arr) { sum = sum + value; } return (double) sum / arr.length; } }

for (int value : arr) は「arr の要素を 1 つずつ value という名前で取り出して、本体を実行する」という意味です。インデックス i が要らないときは、こちらの書き方のほうがバグが入りにくく読みやすいです。中身は同じ sum += value (= sum = sum + value) ですが、arr[i] のような添字アクセスが消える分、見た目がすっきりします。

インデックス i 自体を使いたい場面 (前後の要素を比較する、偶数番目だけ処理する、など) ではインデックス付き for を使い、要素を順番に処理するだけなら拡張 for 文を使う、という使い分けがおすすめです。

よくある間違い

配列の平均を書くとき、入門者がつまずきがちなポイントを 3 つにまとめます。

  • 整数除算のままにしてしまうreturn sum / arr.length; と書くと戻り値の型が double でも、右辺の int / int の結果 intdouble に変換されるだけです。arrayAvg(new int[]{4, 7})5.5 を期待しているのに 5.0 が返ってきて「あれ?」となります。割り算の前に、片方を (double) でキャスト しましょう
  • キャストの位置を間違える(double)(sum / arr.length) は意味がありません。括弧の中で先に int の割り算が走ってしまうからです。(double) sum / arr.length のように、キャストは割り算の左側の値だけに掛けるのが正解です
  • 空配列を素通りさせるif (arr.length == 0) return 0.0; を忘れると、arr.length0 のとき sum / 0 を計算しようとして ArithmeticExceptionNaN が出てきます。割る前に必ず分母が 0 でないかをチェックするのが鉄則です

もうひとつ、arr.lengtharr.length() と書いてしまうミスもよくあります。配列の長さは プロパティ なので () は付けません。String.length() と紛らわしいですが、ここはぐっとこらえて () なしで覚えてください。

エラーが出たら、まずは「分母が 0 になっていないか」「キャストの位置が正しいか」を見るクセを付けましょう。/ by zero という単語をエラーログで見たら 9 割は 0 除算、5.0 のような期待外れの値が出たら整数除算、と当たりを付けられるようになります。

やってみよう

それでは課題に挑戦してみましょう。手順は次のとおりです。

  1. 空配列ガード if (arr.length == 0) return 0.0; をメソッド冒頭に書く
  2. int sum = 0; で合計用の変数を用意する
  3. for 文で arr の要素を全部 sum に足し込む (インデックス付き for でも拡張 for 文でも OK)
  4. return (double) sum / arr.length;double にキャストしてから割り算する
  5. 「実行」ボタンを押してテストが緑になることを確認する

まずは素直なインデックス付き for で書いて、テストが pass することを確認してみてください。pass したら、拡張 for 文 (for (int v : arr)) に書き換えても同じ結果になるか試してみましょう。同じロジックを 2 通りの書き方で実装すると、Javafor の使い分けが体で理解できます。

余力があれば、「最大値」「最小値」を返す arrayMax(int[] arr) arrayMin(int[] arr) を自分で書いてみると、配列ループの感覚がさらに磨かれます。合計・平均・最大・最小・個数の 5 点セットは、これから書く Java コードの 8 割で何らかの形で登場すると思って間違いありません。ここでしっかり手を動かしておきましょう。

よくある質問

Q. Java と他言語の文法はどう違いますか?

A. Java は静的型付けで、変数宣言時に型を明示します(int x = 0;)。中括弧でブロックを表し、文末にセミコロンが必要です。Python の動的型付けや JavaScript の柔軟さに慣れていると最初は窮屈ですが、コンパイル時にバグが発見できる安全性がメリットです。

Q. コードが動かないときに最初に見るべき場所は?

A. コンパイルエラーは行番号とエラー種別(cannot find symbol、incompatible types など)が表示されます。実行時例外はスタックトレースの最初の at ... が原因行です。IDE(IntelliJ / VS Code)の警告も丁寧に潰すと、半分のバグは未然に防げます。

Q. Java の習得後に学ぶべき技術は何ですか?

A. 基本構文を抑えたら java-intermediate(コレクション / ジェネリクス / Stream / 例外)に進み、Spring Boot や Web 開発に展開するのが王道です。クラウド時代は Kotlin / Scala への展開も視野に入りますが、まずは Java 標準を固めるのが効率的です。

次のレッスン

次は 配列の最大値 で、配列の要素を全部足して、要素数で割って平均を求める を学びます。

事前確認 — 進む前に次の 3 つができることを確認しましょう。

  1. 平均 の要点を自分の言葉で説明できる
  2. このレッスンの最小コード (または操作手順) を見ずに書ける
  3. 練習問題やクイズで間違えた箇所を読み直して理解した

理解度チェック (30 秒)

Q. 平均 とは何か、1 文で説明してください。

この章のポイント

A. 本文の「このレッスンで分かること」または冒頭の説明文を見直し、自分の言葉で要約できれば OK。詰まったら本レッスンの最初の H2 セクションを読み返してみましょう。

関連レッスン

要件

  1. クラス名は Solution、メソッド名は arrayAvg で、引数は int[] arr、戻り値は double
  2. 空配列 (arr.length == 0) のときは 0.0 を返すこと (0 除算しない)
  3. 割り算は (double) sum / arr.length のように double にキャストしてから行い、整数除算にならないようにすること

入出力例

test-cases.txt

arrayAvg([4,6])5 arrayAvg([2,2,2])2 arrayAvg([])0 arrayAvg([10,20,30,40])25

ヒント

main.java
main.java
学習モード

メモ

配列の平均

⌘S で保存