PHPのmaxとminは空配列でValueErrorになる?混在型や同値で結果がブレる理由

max()とmin()は、引数(または配列)の中から比較で最大・最小になる値を返す関数です。空配列を渡すとPHP 8.0以降はValueErrorになり、型が混ざった値では「PHPの比較ルール」と「同値なら先頭を返す」という挙動で結果が決まります。

最大値を取るだけの関数なので、あまり警戒していませんでした。ただ、DBやCSVから来た値に対して使うと、空のときに落ちたり、数字のつもりの文字列が混ざって思わぬ値が返ったりします。今回はそのあたりを整理してみます。

空配列を渡すとどうなる?

PHP 8.0以降、空配列を渡すとValueErrorが投げられます。PHP 7系では警告を出してfalseを返していたので、そのつもりで書いたコードは8系で急に例外になります。

<?php
$prices = [];

try {
    echo max($prices);
} catch (ValueError $e) {
    echo $e->getMessage();
    // max(): Argument #1 ($value) must contain at least one element
}

検索結果が0件になりうる場面では、呼ぶ前に空かどうかを見るのが素直です。デフォルト値を決めておけば、呼び出し側でnullを気にしなくて済みます。

<?php
$maxPrice = $prices === [] ? null : max($prices);

// 空なら0として扱ってよいなら、展開で0を混ぜる手もあります
$maxPriceOrZero = max([...$prices, 0]);

後者は「0を下限にする」という意味が入るので、負の値を扱うデータには向きません。そこだけ注意ですね。

同じ値が複数あるとどちらが返る?

比較して等しい値が複数あるときは、先に渡したほうが返ります。値が完全に同じなら気になりませんが、型が違うと差が出ます。

<?php
var_dump(max(1, '1'));   // int(1)
var_dump(max('1', 1));   // string(1) "1"

var_dump(max(1, 1.0));   // int(1)
var_dump(max(1.0, 1));   // float(1)

後続の処理で厳密比較(===)をしていると、この差でテストが通ったり落ちたりします。型をそろえてから渡しておくと、挙動が読みやすくなります。

文字列と数値が混ざるとどう比べられる?

max()の比較は、<や>と同じPHPの通常の比較ルールです。数字だけの文字列どうしは数値として、片方が数字でない文字列なら文字列として比べられます。

<?php
var_dump(max('10', '9'));    // string(2) "10"  数値として比較
var_dump(max('10', '9a'));   // string(2) "9a"  '9a'は数値文字列ではないので文字列比較

var_dump(max(['100', '20', '3x']));  // string(2) "3x"

CSVを読み込んだだけの配列は全部文字列ですし、1行だけ「3x」のような汚れた値が混ざっていても、エラーにはなりません。最大値として「3x」が返ってきて、気づくのは集計結果がおかしいと言われたあとになりがちです。

数値として扱うつもりなら、先にintvalやfilter_varで変換するか、検証してから渡すのが安全です。変換した場合、汚れた値は0などに化けるので、その扱いも決めておく必要があります。

配列どうしを比べると要素数が先に見られる?

max()に配列を複数渡すと、配列が比較されます。このとき先に見られるのは要素数で、要素数が同じときだけ先頭から順に値を比べます。

<?php
var_dump(max([1, 2], [1, 3]));   // [1, 3]  要素数が同じなので値を順に比較
var_dump(max([1, 2, 3], [5]));   // [1, 2, 3]  要素数が多いほうが大きい扱い

「二次元配列の最大の行を取る」つもりで書いたmax()が、要素数の多い行を返してしまう、というパターンです。行の中の特定の値で選びたいときは、array_columnで取り出してからmax()に渡します。

最大値のキーはどう取る?

max()は値だけを返し、キーは返しません。キーが欲しいときは、返ってきた値でarray_searchを引き直します。ただしarray_searchは最初に見つかったキーしか返さないので、同じ最大値が複数ある場合はarray_keysで全部取ります。

<?php
$scores = ['a' => 3, 'b' => 7, 'c' => 7];

var_dump(array_search(max($scores), $scores));  // string(1) "b"
var_dump(array_keys($scores, max($scores)));    // ['b', 'c']

array_keysの第2引数は緩い比較で探すので、数値と文字列が混ざる配列ではここでも先ほどの比較ルールが効いてきます。型がそろったデータで使うぶんには、これで十分だと思います。

まとめ

max()とmin()は、空配列でValueError、同値なら先頭、型が混ざればPHPの比較ルール、というところだけ押さえておけばだいぶ安全に使えます。外から来た値に使うときは、先に空チェックと型の変換を挟んでおく、くらいの気持ちでいるのがちょうどいいかもしれません。

よくある質問

Q. max()に空配列を渡したときのPHP 7と8の違いは?
A. PHP 7系は警告を出してfalseを返し、PHP 8.0以降はValueErrorを投げます。8系へ上げるときは、空配列になりうる箇所を洗っておくと安心です。

Q. 最大値の行(キー)を取りたいときはどうしますか?
A. max()で値を取り、array_searchでキーを引くか、同値が複数ありうるならarray_keysで全部取ります。

Q. 文字列の数字が混ざった配列でmax()を使っても大丈夫ですか?
A. 数字だけの文字列なら数値として比較されますが、数字でない文字列が1つ混ざると文字列比較になる組み合わせがあります。先にintvalやfilter_varで型をそろえるほうが確実です。

類似投稿

コメントを残す

メールアドレスが公開されることはありません。 ※ が付いている欄は必須項目です