🐈

【JavaScript】配列の最大値(最小値)を取得するには reduce を使うのがいいらしい

2024/04/16に公開

※この記事はQiitaの本記事にて投稿した記事に加筆・修正し、移行した記事です。

要素が数値のみからなる配列の最大値(もしくは最小値)を取得する方法について調べた結果、いろいろな方法が見つかったので、記録しておきます。

要約

  • 配列の最大値を取得するなら、reduce を使うのが better
  • Math.max.apply(null, array) の使用は限定的
  • スプレッド構文の使用も限定的

最大値(最小値)を取得するのに戸惑った

数値からなる配列の最大値と最小値を取得する方法について、Python でいう max(lst), min(lst) 的なものがあるんだろうと思って一応調べてみたらそうでもなかったので若干戸惑いました。

maxAndmin.py
lst = [1, 2, 3, 4, 5]
max_val = max(lst) # => 5
min_val = min(lst) # => 1

こんな感じでできると思ってた。

取得はできるが、限定的な方法

  • Math.max.apply() を使って取得する方法
    通常、Math.max() メソッド(Math.min() メソッド)は引数として複数の数値を与える必要があり、配列を与えてしまうと NaN を返します。
maxAndmin.js
let ary = [1, 2, 3, 4, 5];
let max_val_NaN = Math.max(ary);       // => NaN
let max_val = Math.max(1, 2, 3, 4, 5); // => 5
let min_val_NaN = Math.min(ary);       // => NaN
let min_val = Math.min(1, 2, 3, 4, 5); // => 1

しかし、以下のように書くと引数として配列を与えても、最大値(最小値)を返します。

maxAndmin.js
let ary = [1, 2, 3, 4, 5];
let max_val = Math.max.apply(null, ary); // => 5
let min_val = Math.min.apply(null, ary); // => 1
  • Math.max() の引数にスプレッド構文を使う
maxAndmin.js
let ary = [1, 2, 3, 4, 5];
let max_val = Math.max(...ary); // => 5
let min_val = Math.min(...ary); // => 1

しかし、これらの方法は限定的な方法で、注意が必要らしい

MDN web docs: Math.max()によるとこんな感じらしいです。

しかし、要素の数が多い大規模な配列では、スプレッド構文 (...) と apply のどちらも、失敗するか誤った値を返す可能性があります。なぜなら、配列の要素を関数の引数として渡そうとしているからです。

つまり、引数として与えた配列を展開して複数の引数としている。というイメージでしょうか?間違ってたらすみません。
この方法に関して、限定的だとしている理由についてはMDN web docs: Function.prototype.apply()に記載がありました。

しかし注意してください。この方法で apply を使う場合、JavaScript エンジンの引数の長さ上限を超えてしまう危険があります。
多すぎる (おおよそ数万個以上だと思って下さい) 引数を与えた結果は、その上限が特に決まっていない (本当に任意の巨大なデータのかたまりに対してさえ) ためエンジンによって (JavaScriptCore ライブラリでは引数の上限は65536であるとハードコーディングされています) 異なります。例外を投げるエンジンも存在します。
さらに酷い場合では、関数へ実際に渡す引数の数を勝手に制限するものもあります。

つまり、最大値(最小値)を求めようとしている今回のパターンにおいて、場合によっては配列の途中までの最大値(最小値)が返ってきてしまうことがあるということみたいです。

maxAndmin.js
// 引数の数が3つまでと制限されている場合
let ary = [5, 2, 3, 1, 10];
let max_val = Math.max.apply(null, ary) // => 5
let min_val = Math.min.apply(null, ary) // => 2

上記において、本来は最大値が 10, 最小値が 1 ですが、どちらも異なる値を返しています。
これは先述したように配列を途中で切っているためです。
まだまだ知識も経験も浅い私からしたらこんな極端なパターンあるのかな?という感じですが、どちらにせよ、より安全な方法があるのならばそちらを使いたいです。

reduce() を使うと安全!

MDN web docs: Math.max()によれば、以下の様にすれば安全だそうです。

maxAndmin.js
const aryMax = function (a, b) {return Math.max(a, b);}
const aryMin = function (a, b) {return Math.min(a, b);}
let ary = [5, 2, 3, 1, 10];
let max = ary.reduce(aryMax); // => 10
let min = ary.reduce(aryMin); // => 1

配列の頭から二つずつ比較して最大値(最小値)を更新していく、というイメージでしょうか。

最後に

最大値(最小値)を取得したいのならば、reduce() を使おう!

参考

MDN web docs: Math.max()
MDN web docs: Function.prototype.apply()
MDN web docs: Array.prototype.reduce()
MDN web docs: スプレッド構文
Qiita: JavaScriptで配列の最大値・最小値求めるならreduceで
reduce() を使うべき理由や実際のケースを詳しく解説してくださっており、非常に参考になりました。ありがとうございました。

Discussion