Help us understand the problem. What is going on with this article?

配列の中の最大値を取得する(NaN, null対応)

More than 5 years have passed since last update.

普通に扱う場合、もはやイディオムのコレ

Math.max.apply(null, [-100, 0, 3.14, 0xFF, 1000]); // => 1000
// ※ Mathの関数のコンテキスト(applyやcallの第一引数)はなんでもいいらしい

注意点はNaNnull nudefinedがあるとき

NaN

NaNは何と比較してもNaNしか返らない

Math.max(100, NaN, -100); // => NaN
Math.max(NaN, Infinity); // => NaN (Infinityと比較しても同じ)
Math.min(100, NaN, -100); // => NaN (Math.minの比較でも同じ)

undefinedとnull

nullundefinedは暗黙の型変換が行われる

// それぞれの数値型への暗黙の型変換
+null; // => 0
+undefined; // => NaN

従って

// undefined
Math.max(1, undefined, null); // => NaN (undefinedのNaNが返る)

// null
Math.max(1, 3, 5, 7, null, 11, 13); // => 13 (おや?大丈夫じゃね?ところが↓)
Math.max(-2, -4, -8, -16, null, -64); // => 0 (nullが0に変換されるので最大は0)

nullなどを含む配列から最大値を得るにはどうするか

1. 余計な要素を取り除く方法

1.1. 地道にループして余計な要素のない配列をつくってから比較

var array = [/* これにデータが詰まってるとする */];

var newArray = [];
for (var i = 0, l = array.length; i < l; i++) {
    var n = array[i];
    if (n != null && !isNaN(n)) {
        newArray.push(n);
    }
}

var max = Math.max.apply(null, newArray);

何かすごく余計なことをしている気がする…。

1.2. 余計な要素は無視して比較しながら求める

var array = [/* これにデータが詰まってるとする */];
var max;

for (var i = 0, l = array.length; i < l; i++) {
    var n = array[i];
    if (n != null && !isNaN(n)) {
        if (max) {
            max = Math.max(max, n);
        } else {
            max = n;
        }
    }
}

一番素直なやり方…。

1.3. 余計な要素を配列から取り除いて比較 ※糞コード注意

var array = [/* これにデータが詰まってるとする */];

var clone = array.concat();
var i;
while ((i = clone.indexOf(NaN)) !== -1) clone.splice(i, 1); // 糞糞糞糞糞糞
while ((i = clone.indexOf(undefined)) !== -1) clone.splice(i, 1);
while ((i = clone.indexOf(null)) !== -1) clone.splice(i, 1);

var max = Math.max.apply(null, clone);

トリッキーなことをしてみようと思ったけど、これは激遅の上に 取得 できない糞コード。

どこが臭うかというと

  • Array.prototype.indexOfがIE9以上でしか使えない
  • NaN undefined nullがあればあるほどループの回数が増える。線形検索を各要素で行うので、最悪[要素数x3]のループが起こる。
  • indexOfの検索は通常の比較演算と同じ振る舞いなので、NaN絶対にヒットしない 。つまりNaNは取り除けないので、配列にNaNが含まれていれば絶対にNaN返ってくる 。 ←糞糞糞糞糞糞糞糞糞糞糞糞

2. 並び替えてから取得する

2.1. sortを使う。

var array = [/* これにデータが詰まってるとする */];
var clone = array.concat();
clone.sort(function (a, b) {
    var aIsNotNumeric = a == null || isNaN(a);
    var bIsNotNumeric = b == null || isNaN(b);
    if (aIsNotNumeric && bIsNotNumeric) {
        return 0;
    } else if (aIsNotNumeric) {
        return 1;
    } else if (bIsNotNumeric) {
        return -1;
    } else {
        return b - a; // min評価の場合は a - b
    }
});
var max = clone[0];

何をしているかというと、基本的には降順ソートをして最初の要素を取得するというもの。

この場合も単純にやってしまうとNaNnullのでおかしくなる。

配列のソートは ab

  • aを後ろにまわしたい場合1
  • bを後ろにまわしたい場合-1
  • 何もしない場合0

という戻り値のコントロールを行えばよいので、NaN null undefinedが現れたらとにかく配列の後ろに送るようにソートすればよい。


速度評価

0xFFFF個要素のある配列を作って、それから上記の処理を100回繰り返したときのパフォーマンス速度。

Chome

[1.1.] --- 521.0349999979371ms
[1.2.] --- 560.3829999890877ms
[1.3.] --- 2905.022999999346ms
[2.1.] --- 44.17900000407826ms

Firefox

[1.1.] -> 123 --- 49.050766000000294ms
[1.2.] -> 123 --- 45.754089999999906ms
[1.3.] -> NaN --- 3067.4649800000007ms
[2.1.] -> 123 --- 661.0221620000002ms

ChromeとFirefoxでこんなにも結果が異なるとは…。

※1.3.は評価する価値もないですホントは。

結論

最初Chromeだけで試してソート方式がいいなと早とちりしてましたが、@gocho さんからご指摘をいただいてFirefoxでの速度が変わると教えていただきました。

ベスト ってないのかぁ。。。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした