普通に扱う場合、もはやイディオムのコレ
Math.max.apply(null, [-100, 0, 3.14, 0xFF, 1000]); // => 1000
// ※ Mathの関数のコンテキスト(applyやcallの第一引数)はなんでもいいらしい
注意点はNaN
やnull
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
null
とundefined
は暗黙の型変換が行われる
// それぞれの数値型への暗黙の型変換
+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];
何をしているかというと、基本的には降順ソートをして最初の要素を取得するというもの。
この場合も単純にやってしまうとNaN
やnull
のでおかしくなる。
配列のソートは a
と b
で
-
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での速度が変わると教えていただきました。
ベスト ってないのかぁ。。。