何をしたいユーティリティか:「配列の平均算出」
ここでの「平均算出」は、配列の中の数値を全部足して、その合計を要素数で割る処理です。
学校で習った「平均」と同じですが、プログラムでやるときは「空配列のときどうするか」「数値でないものが混ざったらどうするか」をきちんと決める必要があります。
業務だと、例えば次のような場面で使います。
売上金額の平均を出したい。
処理時間の平均を出したい。
テストスコアの平均点を出したい。
ここでは、まず「素の数値配列の平均」、次に「オブジェクト配列から特定項目の平均」、最後に「任意の計算結果の平均」という順でかみ砕いていきます。
基本形:数値配列の平均を求める
合計 ÷ 件数、をコードに落とす
平均の定義はとてもシンプルです。
「合計 ÷ 件数」です。
これをそのままコードにすると、次のようになります。
function averageNumbers(array) {
if (!Array.isArray(array) || array.length === 0) {
return undefined;
}
let total = 0;
let count = 0;
for (const value of array) {
if (typeof value !== "number" || Number.isNaN(value)) {
continue;
}
total += value;
count += 1;
}
if (count === 0) {
return undefined;
}
return total / count;
}
JavaScript重要なポイントをかみ砕いて説明する
最初に「配列かどうか」と「長さが 0 かどうか」を見ています。
空配列の平均は定義しづらいので、ここでは undefined を返す設計にしています。
「平均が存在しない」という状態を、呼び出し側で判定できるようにするためです。
ループの中では、total に足し込み、count を 1 ずつ増やしています。
ここでのポイントは、「配列の長さ」ではなく「実際に平均に含めた要素数」を count にしていることです。
数値でないものや NaN はスキップしているので、count は「有効な数値の個数」になります。
最後に、count が 0 のままなら undefined を返し、そうでなければ total / count を返します。
これで、「有効な数値が一つもなかった場合」にも安全に動きます。
実際の動き
averageNumbers([10, 20, 30]);
// 20
averageNumbers([1, 2, 3, 4, 5]);
// 3
averageNumbers([]);
// undefined
averageNumbers([10, "20", 30]);
// (10 + 30) / 2 = 20
JavaScriptreduce を使った書き方と、その意味
reduce 版の実装
同じことは reduce を使っても書けます。
function averageNumbersReduce(array) {
if (!Array.isArray(array) || array.length === 0) {
return undefined;
}
const { total, count } = array.reduce(
(acc, value) => {
if (typeof value !== "number" || Number.isNaN(value)) {
return acc;
}
return {
total: acc.total + value,
count: acc.count + 1,
};
},
{ total: 0, count: 0 }
);
if (count === 0) {
return undefined;
}
return total / count;
}
JavaScriptreduce の初期値 { total: 0, count: 0 } が、「合計」と「件数」のスタート地点です。
コールバックの中で、「新しい値を足した total と、1 増えた count」を返し続けることで、最後に { total, count } が完成します。
やっていることはループ版と同じで、「書き方を圧縮しただけ」と捉えると理解しやすくなります。
オブジェクト配列から「特定の項目の平均」を出す
価格やスコアの平均を出したいケース
業務では、次のような配列がよく出てきます。
const products = [
{ id: 1, price: 1000 },
{ id: 2, price: 500 },
{ id: 3, price: 1500 },
];
JavaScriptここから「price の平均」を出したいときのユーティリティです。
function averageByKeyNumber(array, key) {
if (!Array.isArray(array) || array.length === 0) {
return undefined;
}
let total = 0;
let count = 0;
for (const item of array) {
if (!item || typeof item !== "object") {
continue;
}
const value = item[key];
if (typeof value !== "number" || Number.isNaN(value)) {
continue;
}
total += value;
count += 1;
}
if (count === 0) {
return undefined;
}
return total / count;
}
JavaScriptここでも、「合計」と「件数」を同時に管理しています。
数値でないものはスキップし、「有効な数値だけ」を平均の対象にしています。
実際の動き
const products = [
{ id: 1, price: 1000 },
{ id: 2, price: 500 },
{ id: 3, price: 1500 },
];
averageByKeyNumber(products, "price");
// (1000 + 500 + 1500) / 3 = 1000
JavaScript任意の「評価関数」の平均を出す(例:単価×数量の平均)
単純なキーでは足りない場合
「そのままの値」ではなく、「計算した結果」の平均が欲しいことも多いです。
例えば、「金額=単価×数量」の平均金額などです。
そのときは、「要素から平均対象の数値を取り出す関数」を渡せるようにします。
function averageBy(array, valueFn) {
if (!Array.isArray(array) || array.length === 0) {
return undefined;
}
let total = 0;
let count = 0;
for (const item of array) {
const value = valueFn(item);
if (typeof value !== "number" || Number.isNaN(value)) {
continue;
}
total += value;
count += 1;
}
if (count === 0) {
return undefined;
}
return total / count;
}
JavaScriptvalueFn は、「要素を受け取って、その要素から“平均したい数値”を返す関数」です。
実際の使い方
例えば、次のような明細があるとします。
const items = [
{ id: 1, price: 1000, quantity: 2 }, // 金額 2000
{ id: 2, price: 500, quantity: 3 }, // 金額 1500
{ id: 3, price: 2000, quantity: 1 }, // 金額 2000
];
JavaScript金額(price × quantity)の平均を出したい場合は、こう書けます。
averageBy(items, (item) => item.price * item.quantity);
// (2000 + 1500 + 2000) / 3 = 1833.333...
JavaScript同じ averageBy を使って、「価格の平均」「数量の平均」なども簡単に書けます。
averageBy(items, (item) => item.price);
// 単価の平均
averageBy(items, (item) => item.quantity);
// 数量の平均
JavaScript実務で意識してほしい設計のポイント
空配列や「有効な値がない」場合の扱い
平均は「合計 ÷ 件数」なので、「件数が 0」のときは計算できません。
ここをどう扱うかを、ユーティリティ側で決めておくのが大事です。
今回の実装では、次のようにしています。
配列が空なら undefined。
数値として扱える要素が一つもなければ undefined。
これにより、「平均が定義できない状態」を undefined で表現できます。
呼び出し側は、「undefined なら平均なし」として分岐を書けばよくなります。
もし「必ず 0 を返してほしい」ような要件なら、ラッパー関数を用意してもよいです。
function averageNumbersOrZero(array) {
const avg = averageNumbers(array);
return avg == null ? 0 : avg;
}
JavaScript「数値でないものが混ざったとき」の方針
現実のデータには、"100" のような文字列や null が混ざることがあります。
ここでの方針は、「無理に数値に変換しない」「おかしいものはスキップする」です。
Number("100") のように変換してしまうと、"abc" のような値が来たときに NaN になり、合計や平均が NaN になってしまいます。
今回の実装では、typeof value === "number" と Number.isNaN(value) を使って、
「ちゃんとした数値だけ」を対象にしています。
それでも問題があるようなら、「ログを出す」「バリデーションで弾く」といった別の層で対処するのがよいです。
合計ユーティリティとの関係
平均は「合計 ÷ 件数」なので、実は「合計算出ユーティリティ」と非常に相性がいいです。
例えば、次のように分けることもできます。
合計を出す関数(sumNumbers, sumBy)。
件数を数える関数。
それらを組み合わせて平均を出す関数。
設計としては、「合計」と「平均」を別々のユーティリティにしておき、
必要に応じて組み合わせるほうが再利用性が高くなります。
少し手を動かして感覚をつかむ
コンソールで、次のようなコードを実際に打ってみてください。
averageNumbers([10, 20, 30]);
averageNumbersReduce([10, 20, 30]);
const products = [
{ id: 1, price: 1000 },
{ id: 2, price: 500 },
{ id: 3, price: 1500 },
];
averageByKeyNumber(products, "price");
const items = [
{ id: 1, price: 1000, quantity: 2 },
{ id: 2, price: 500, quantity: 3 },
{ id: 3, price: 2000, quantity: 1 },
];
averageBy(items, (item) => item.price * item.quantity);
JavaScript「どの値が平均の対象になっているか」「空や不正な値のときにどう振る舞うか」を、自分の目で確認してみてください。
そのうえで、自分のプロジェクトに
export function averageNumbers(...) { ... }
export function averageNumbersReduce(...) { ... }
export function averageByKeyNumber(...) { ... }
export function averageBy(...) { ... }
JavaScriptのような関数を置き、
「配列から平均を出したくなったら、必ずこの“平均算出ユーティリティ”を通す」
というルールを作ってみてください。
そうすると、あなたの集計コードは、「なんとなく足して割る」から、「意図と安全性を備えた業務レベルの実装」に一段ステップアップします。
