LoginSignup
34
35

More than 5 years have passed since last update.

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

Last updated at Posted at 2013-08-16

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

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での速度が変わると教えていただきました。

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

34
35
19

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
34
35