JavaScript Tips | 配列ユーティリティ:平均算出

JavaScript JavaScript
スポンサーリンク

何をしたいユーティリティか:「配列の平均算出」

ここでの「平均算出」は、配列の中の数値を全部足して、その合計を要素数で割る処理です。
学校で習った「平均」と同じですが、プログラムでやるときは「空配列のときどうするか」「数値でないものが混ざったらどうするか」をきちんと決める必要があります。

業務だと、例えば次のような場面で使います。
売上金額の平均を出したい。
処理時間の平均を出したい。
テストスコアの平均点を出したい。

ここでは、まず「素の数値配列の平均」、次に「オブジェクト配列から特定項目の平均」、最後に「任意の計算結果の平均」という順でかみ砕いていきます。


基本形:数値配列の平均を求める

合計 ÷ 件数、をコードに落とす

平均の定義はとてもシンプルです。
「合計 ÷ 件数」です。
これをそのままコードにすると、次のようになります。

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
JavaScript

reduce を使った書き方と、その意味

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;
}
JavaScript

reduce の初期値 { 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;
}
JavaScript

valueFn は、「要素を受け取って、その要素から“平均したい数値”を返す関数」です。

実際の使い方

例えば、次のような明細があるとします。

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

のような関数を置き、

「配列から平均を出したくなったら、必ずこの“平均算出ユーティリティ”を通す」

というルールを作ってみてください。
そうすると、あなたの集計コードは、「なんとなく足して割る」から、「意図と安全性を備えた業務レベルの実装」に一段ステップアップします。

タイトルとURLをコピーしました