sort() は既定で文字列として比べるため、数値を渡すと [1, 10, 2] のように並びます。比較関数の書き方、元の配列を壊さない方法、日本語やオブジェクトの並び替えまでまとめます。
答え
比較関数を渡します。 そして元の配列を壊さないように、先に複製します。
example.js
const nums = [10, 1, 5, 100, 2];
const asc = [...nums].sort((a, b) => a - b);
console.log(asc);
console.log(nums);
出力書き換えて実行できます
[ 1, 2, 5, 10, 100 ]
[ 10, 1, 5, 100, 2 ]
example.js
const nums = [10, 1, 5, 100, 2];
console.log([...nums].sort((a, b) => b - a));
出力書き換えて実行できます
[ 100, 10, 5, 2, 1 ]
降順は b - a です。
javascript
const nums = [10, 1, 5, 100, 2];
console.log(nums.sort());
[ 1, 10, 100, 2, 5 ]
example.js
const nums = [3, 1, 2];
console.log(nums.map(n => n).sort((a, b) => {
console.log(`${a} と ${b} を比べる → ${a - b}`);
return a - b;
}));
出力書き換えて実行できます
1 と 3 を比べる → -2
2 と 1 を比べる → 1
2 と 3 を比べる → -1
2 と 1 を比べる → 1
[ 1, 2, 3 ]
なぜそのまま並べると狂うのか
sort() は、比較関数を渡さないと全部を文字列に変換してから比べます。
javascript
const nums = [3, 1, 2];
const sorted = nums.sort((a, b) => a - b);
console.log(sorted);
console.log(nums);
console.log(sorted === nums);
[ 1, 2, 3 ]
[ 1, 2, 3 ]
true
example.js
const nums = [3, 1, 2];
console.log([...nums].sort((a, b) => a - b));
console.log(nums.toSorted((a, b) => a - b));
console.log(nums);
出力書き換えて実行できます
[ 1, 2, 3 ]
[ 1, 2, 3 ]
[ 3, 1, 2 ]
文字列として並べると '10' は '2' より前になります。
1文字目の '1' と '2' を比べているからです。辞書順です。
仕様にもそう書いてあります。比較関数が undefined のとき、
要素を文字列に変換してから比べる、と定められています。
比較関数の決まり
比較関数は2つの値を受け取り、数値を返します。
| 返す値 |
意味 |
| 負の数 |
a を前にする |
0 |
順序を変えない |
| 正の数 |
a を後ろにする |
a - b が昇順になるのは、a のほうが小さいときに負の数になるからです。
example.js
const words = ['banana', 'apple', 'cherry'];
console.log([...words].sort());
出力書き換えて実行できます
[ 'apple', 'banana', 'cherry' ]
example.js
const names = ['さとう', 'あおき', 'たなか', 'ざっそう'];
console.log([...names].sort());
console.log([...names].sort((a, b) => a.localeCompare(b, 'ja')));
出力書き換えて実行できます
[ 'あおき', 'さとう', 'ざっそう', 'たなか' ]
[ 'あおき', 'ざっそう', 'さとう', 'たなか' ]
3件なのに4回比べています。同じ組を2回見てもいます。
どの順で何回比べるかは、仕様で決まっていません。 処理系に任されています。
比較関数はいつどう呼ばれても同じ答えを返すように書いてください。
中で数を数えたり、外の変数を書き換えたりすると結果が読めなくなります。
真偽値を返してはいけません。 (a, b) => a > b は true / false になり、
数値に直すと 1 / 0 です。「前にする」を表せません。
元の配列を壊さない
sort() は破壊的メソッドです。呼んだ配列そのものを並び替えます。
example.js
const words = ['B', 'a', 'C', 'b'];
console.log([...words].sort());
console.log([...words].sort((a, b) => a.localeCompare(b, 'ja')));
出力書き換えて実行できます
[ 'B', 'C', 'a', 'b' ]
[ 'a', 'b', 'B', 'C' ]
example.js
const users = [
{ name: 'あかり', age: 30 },
{ name: 'ゆうと', age: 22 },
{ name: 'はると', age: 41 },
];
const byAge = [...users].sort((a, b) => a.age - b.age);
console.log(byAge.map(u => `${u.name}(${u.age})`));
出力書き換えて実行できます
[ 'ゆうと(22)', 'あかり(30)', 'はると(41)' ]
戻り値は同じ配列です。別物ではありません。
元を残したいなら、次のどちらかにします。
example.js
const users = [
{ name: 'ゆうと' },
{ name: 'あかり' },
];
console.log([...users].sort((a, b) => a.name.localeCompare(b.name, 'ja')).map(u => u.name));
出力書き換えて実行できます
[ 'あかり', 'ゆうと' ]
example.js
const items = [
{ key: 'b', n: 1 },
{ key: 'a', n: 2 },
{ key: 'a', n: 3 },
];
console.log([...items].sort((x, y) => x.key.localeCompare(y.key)).map(i => i.key + i.n));
出力書き換えて実行できます
[ 'a2', 'a3', 'b1' ]
toSorted() は新しい配列を返す書き方です(ES2023)。
Node 20 / Chrome 110 / Safari 16 より前の環境では使えません。迷ったら、スプレッド構文で複製してから sort() を呼ぶほうが確実です。
文字列を並び替える
英数字だけなら比較関数は要りません。既定の辞書順で並びます。
example.js
const nums = [3, undefined, 1];
console.log([...nums].sort((a, b) => a - b));
出力書き換えて実行できます
[ 1, 3, undefined ]
javascript
const nums = [3, NaN, 1, 2];
console.log([...nums].sort((a, b) => a - b));
[ 3, NaN, 1, 2 ]
日本語では期待どおりになりません。 文字コード順に並ぶためです。
example.js
const nums = [3, NaN, 1, 2];
const clean = nums.filter(n => !Number.isNaN(n));
console.log([...clean].sort((a, b) => a - b));
出力書き換えて実行できます
[ 1, 2, 3 ]
「さとう」と「ざっそう」の順が入れ替わります。
- 既定 …
さ(U+3055)と ざ(U+3056)を別の文字として比べるので、
1文字目で決まってしまう
localeCompare … ざ を さ の濁った形として扱うので、
2文字目の っ と と で決まる。国語辞典と同じ並び
濁点・半角と全角・大文字と小文字が混ざると、こうした差が出ます。
日本語を並べるときは localeCompare を使ってください。
既定では大文字が全部先に来ます。ASCII では大文字(65〜)が小文字(97〜)より小さいためです。
localeCompare なら文字として同じものをまとめ、a b B C の順になります。
オブジェクトの配列を並び替える
比べたい値を取り出して、同じように書きます。
名前で並べるなら localeCompare です。
同じ値の順序は保たれる
比較関数が 0 を返した組は、元の並び順のままです(安定ソート。ES2019 以降は仕様で保証)。
a2 と a3 の順序が入れ替わりません。2段階で並べたいときに使えます
(先に第2キーで並べ、次に第1キーで並べる)。
undefined は必ず最後に行く
undefined は比較関数に渡されません。問答無用で末尾に置かれます。
一方 NaN は渡されます。NaN との引き算は NaN になり、
比較関数が数値を返さないので並びが定まりません。
並び替える前に弾いてください。
NaN の比較については
なぜ NaN === NaN は false なのか にまとめてあります。
比較関数の決まり、安定ソートの保証、undefined の扱いは用語のページにまとめてあります。
→ Array.prototype.sort() と toSorted()