JavaScript Tips | 配列ユーティリティ:最大値取得

JavaScript JavaScript
スポンサーリンク

何をしたいユーティリティか:「配列の最大値取得」

ここでの「最大値取得」は、配列の中で一番大きい値を取り出す処理です。
一番シンプルなのは「数値配列の最大値」ですが、業務では「オブジェクト配列の中で、特定の項目が最大のもの」を取りたいことも多いです。

例としては、次のような場面があります。

売上リストから「売上金額が最大の値」や「売上金額が最大のレコード」を取りたい。
スコア一覧から「最高スコア」を取りたい。
ログの中から「処理時間が最大のもの」を見つけたい。

これを毎回手書きするのではなく、「最大値取得ユーティリティ」としてまとめておくと、コードが読みやすくなります。


一番基本:数値配列の最大値を取得する

Math.max とスプレッド構文を使う方法

JavaScript には Math.max という「引数の中で最大の数値を返す」関数があります。
配列をそのまま渡すことはできませんが、スプレッド構文を使えば簡単に書けます。

function maxNumber(array) {
  if (!Array.isArray(array) || array.length === 0) {
    return undefined;
  }

  return Math.max(...array);
}
JavaScript

この関数は、「数値だけが入っている配列」を前提にしています。

空配列や配列でないものが来たときは undefined を返すようにしておくと、呼び出し側で「最大値が存在しない」という状態を判定しやすくなります。

実際の動き

maxNumber([10, 5, 30, 7]);
// 30

maxNumber([-5, -10, -3]);
// -3

maxNumber([]);
// undefined
JavaScript

ループで最大値を求める(考え方の基本)

自分で最大値を更新していく書き方

Math.max(...array) は便利ですが、「中で何が起きているか」を理解するために、ループで書いてみます。

function maxNumberLoop(array) {
  if (!Array.isArray(array) || array.length === 0) {
    return undefined;
  }

  let max = array[0];

  for (let i = 1; i < array.length; i++) {
    const value = array[i];
    if (value > max) {
      max = value;
    }
  }

  return max;
}
JavaScript

ここでやっていることはとてもシンプルです。

最初の要素を「暫定の最大値」として max に入れる。
2 番目以降の要素を順に見て、「今の max より大きければ max を更新する」。
最後まで見終わったときの max が「配列全体の最大値」になる。

この「暫定値を持っておいて、条件を満たしたら更新する」というパターンは、最大値だけでなく、最小値や合計値などでもよく使う基本形です。


オブジェクト配列から「特定の項目の最大値」を取る

「売上金額が最大の値」だけ欲しい場合

業務では、次のような配列がよく出てきます。

const sales = [
  { id: 1, amount: 1000 },
  { id: 2, amount: 5000 },
  { id: 3, amount: 3000 },
];
JavaScript

ここから「amount の最大値だけ欲しい」場合は、まず amount だけを取り出してから maxNumber に渡す、という方法もあります。

function maxByKeyNumber(array, key) {
  if (!Array.isArray(array) || array.length === 0) {
    return undefined;
  }

  let max = undefined;

  for (const item of array) {
    if (!item || typeof item !== "object") continue;

    const value = item[key];

    if (typeof value !== "number") continue;

    if (max === undefined || value > max) {
      max = value;
    }
  }

  return max;
}
JavaScript

この関数は、「指定したキーの値が数値であるものだけ」を対象にして、その中の最大値を返します。

実際の動き

const sales = [
  { id: 1, amount: 1000 },
  { id: 2, amount: 5000 },
  { id: 3, amount: 3000 },
];

maxByKeyNumber(sales, "amount");
// 5000
JavaScript

「最大値を持つ要素そのもの」を取りたい場合

一番売上が大きいレコードを丸ごと欲しい

多くの場合、「最大値そのもの」だけでなく、「最大値を持つレコード全体」が欲しくなります。

例えば、「売上金額が最大のレコード」を取りたいときです。

function maxItemByKey(array, key) {
  if (!Array.isArray(array) || array.length === 0) {
    return undefined;
  }

  let maxItem = undefined;
  let maxValue = undefined;

  for (const item of array) {
    if (!item || typeof item !== "object") continue;

    const value = item[key];

    if (typeof value !== "number") continue;

    if (maxValue === undefined || value > maxValue) {
      maxValue = value;
      maxItem = item;
    }
  }

  return maxItem;
}
JavaScript

ここでは、「最大値」と「最大値を持つ要素」の両方を持ちながらループしています。

maxValue は「今まで見た中での最大の数値」。
maxItem は「その最大値を持っていたレコード」。

新しい要素を見たときに、「その要素の値が maxValue より大きければ、両方を更新する」という流れです。

実際の動き

const sales = [
  { id: 1, amount: 1000 },
  { id: 2, amount: 5000 },
  { id: 3, amount: 3000 },
];

maxItemByKey(sales, "amount");
// { id: 2, amount: 5000 }
JavaScript

これで、「一番売上が大きいレコード」をそのまま扱えるようになります。


任意の「評価関数」で最大値を決める

key ではなく「関数」で柔軟に書く

「どの値を基準に最大を決めるか」を、キー名ではなく「関数」で渡せるようにすると、表現力が一気に上がります。

function maxBy(array, scoreFn) {
  if (!Array.isArray(array) || array.length === 0) {
    return undefined;
  }

  let bestItem = undefined;
  let bestScore = undefined;

  for (const item of array) {
    const score = scoreFn(item);

    if (score == null || Number.isNaN(score)) {
      continue;
    }

    if (bestScore === undefined || score > bestScore) {
      bestScore = score;
      bestItem = item;
    }
  }

  return bestItem;
}
JavaScript

scoreFn は、「要素を受け取って、その要素の“評価値(スコア)”を返す関数」です。
このスコアが最大の要素を返す、というユーティリティになっています。

実際の使い方

売上金額で最大を取りたいなら、こう書けます。

const maxSale = maxBy(sales, (item) => item.amount);
// { id: 2, amount: 5000 }
JavaScript

「売上金額 ÷ コスト」のような複雑な指標で最大を取りたい場合も、関数の中で自由に計算できます。

const products = [
  { id: 1, price: 1000, cost: 800 },
  { id: 2, price: 2000, cost: 1200 },
  { id: 3, price: 1500, cost: 900 },
];

const best = maxBy(products, (p) => (p.price - p.cost) / p.cost);
// 利益率が最大の商品が返る
JavaScript

実務で意識してほしい設計のポイント

「空のときどうするか」を必ず決める

最大値取得で一番ハマりやすいのは、「空配列から最大値を取ろうとしておかしくなる」パターンです。

Math.max(...[])-Infinity を返します。
これをそのまま使うと、「最大値が存在しないのに、なぜか -Infinity という値が入っている」という不自然な状態になります。

ユーティリティ側で、

空配列なら undefined を返す。
オブジェクト配列で「数値が一つもなかった」場合も undefined を返す。

と決めておくと、呼び出し側は「undefined なら最大値なし」と判断できて、バグを減らせます。

「最大値そのもの」と「最大値を持つ要素」を分けて考える

業務では、

最大値だけ欲しい場面。
最大値を持つレコード全体が欲しい場面。

の両方があります。

関数を分けておくと意図が明確になります。

maxNumber → 数値配列の最大値。
maxByKeyNumber → オブジェクト配列の特定キーの最大値。
maxItemByKey / maxBy → 最大値を持つ要素そのもの。

こうしておくと、「この関数は何を返すのか」が名前だけで分かるようになります。

「比較のルール」をユーティリティに閉じ込める

最大値取得は、「何を基準に大きいとみなすか」という比較ルールとセットです。

単純な数値なら > で十分ですが、
日付文字列や複合的な指標を扱う場合は、比較ロジックが少し複雑になります。

そのときに、画面やサービス層のあちこちに比較ロジックを書き散らすのではなく、

maxBy のようなユーティリティに「比較の基準(scoreFn)」を集約する。

という設計にしておくと、あとから仕様を変えたいときにも 1 箇所を直すだけで済みます。


少し手を動かして感覚をつかむ

コンソールで、次のようなコードを実際に打ってみてください。

maxNumber([10, 5, 30, 7]);
maxNumberLoop([10, 5, 30, 7]);

const sales = [
  { id: 1, amount: 1000 },
  { id: 2, amount: 5000 },
  { id: 3, amount: 3000 },
];

maxByKeyNumber(sales, "amount");
maxItemByKey(sales, "amount");

const products = [
  { id: 1, price: 1000, cost: 800 },
  { id: 2, price: 2000, cost: 1200 },
  { id: 3, price: 1500, cost: 900 },
];

maxBy(products, (p) => (p.price - p.cost) / p.cost);
JavaScript

「どの値が最大として選ばれているか」「どのレコードが“最大のもの”として返ってきているか」を、自分の目で確認してみてください。

そのうえで、自分のプロジェクトに

export function maxNumber(...) { ... }
export function maxByKeyNumber(...) { ... }
export function maxItemByKey(...) { ... }
export function maxBy(...) { ... }
JavaScript

のような関数を置き、

「配列から最大値を取りたくなったら、必ずこの“最大値取得ユーティリティ”を通す」

というルールを作ってみてください。
それだけで、あなたの「最大値を探すコード」は、場当たり的な書き方から、意図と一貫性を備えた業務レベルの実装に変わっていきます。

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