はじめに
JavaScriptでObjectのキーをその要素でソートしたいときに詰まったことのまとめです.
特に,Objectの要素にNaN
が含まれていたり,要素が存在しない場合の対処法です.
問題
const data = {
"1": {"p1": 0.2, "p2": 1, "p3": 0},
"2": {"p1": 0.7, "p2": "-", "p3": 5},
"3": {"p1": 0.3, "p2": -1},
"4": {"p1": 0.1, "p2": 3, "p3": 1}
};
こんなObjectがあったときにObject.keys(x)
を要素(すなわちp1, p2, p3
)の値をキーとしてソートしたい.
Array.prototype.sort
特に問題のないケース
上の例でp1
でソートしたいとき.data[k]["p1"]
が全て存在し,全て数値であるような場合は簡単.
let arr = Object.keys(data);
console.log(arr); // => ["1", "2", "3", "4"]
const cmp = (k1, k2) => {
return data[k1]["p1"] - data[k2]["p1"];
}
arr.sort(cmp);
console.log(arr); // => ["4", "1", "3", "2"]
compareFunction
でObjectの値を参照すればOK.
要素にNaNが含まれているとき
上の例でp2
でソートしたいとき.data[k]["p1"]
が全て存在するが,NaNが混ざっている場合(isNaN(data["2"]["p2"])===true
).
そのままやると...
let arr = Object.keys(data);
console.log(arr); // => ["1", "2", "3", "4"]
arr.sort((k1, k2) => data[k1]["p2"] - data[k2]["p2"]);
console.log(arr); // => ["1", "2", "3", "4"]
NaN
に対応する必要がある.NaN
の要素が一番うしろにソートされるように書いてみる.
let arr = Object.keys(data);
console.log(arr); // => ["1", "2", "3", "4"]
const cmp2 = (k1, k2) => {
const [t1, t2] = [isNaN(data[k1]["p2"]), isNaN(data[k2]["p2"])];
if (t1 && t2) // case 1
return 0;
else if (t1 || t2) // case 2
return t1 ? 1 : -1;
else // case 3
return data[k1]["p2"] - data[k2]["p2"];
};
arr.sort(cmp2);
console.log(arr); // => ["3", "1", "4", "2"]
- case 1: 比較される2つの要素が両方
NaN
のとき.2つは等しいので0を返す. - case 2: どちらか一方が
NaN
のとき.return t1 ? 1 : -1;
はdata[k1]["p2]
がNaN
であるとき,k1
を後ろに起きたいので,1を返す. - case 3: 2つとも数値のとき.これははじめの場合と同じ.
要素が存在しない場合
上の例でp3
でソートしたいとき.data["3"]["p3"]
が存在しない.このときisNaN(data["3"]["p3"])===true
となるので,NaN
の場合と同じように扱える.
よってこの場合は簡単
一般化してみる
オブジェクトとキーを渡してソートされた配列を返す関数を書いてみる.
const sortObject = (obj, sortKey, reverse=false) => {
const sign = reverse ? -1 : 1;
const cmp = (k1, k2) => {
const [t1, t2] = [isNaN(data[k1][sortKey]), isNaN(data[k2][sortKey])];
if (t1 && t2)
return 0;
else if (t1 || t2)
return t1 ? 1 : -1;
else
return sign * (data[k1][sortKey] - data[k2][sortKey]);
};
return Object.keys(obj).slice().sort(cmp);
};
console.log(sortObject(data, "p1")); // => ["1", "2", "3", "4"]
console.log(sortObject(data, "p2")); // => ["3", "1", "4", "2"]
console.log(sortObject(data, "p3")); // => ["1", "4", "2", "3"]
ただし,これはNaN
と非存在要素の比較はしてない.つまり,それらの順番は多分保証されない(知らんけど).