sort() は比較関数を渡さないと文字列として並べます。比較関数が返すべき値、真偽値を返すと並ばない理由、undefined と穴の位置、安定ソートの保証までを実行して確かめます。
sort() は配列を並べ替えます。元の配列を書き換え、同じ配列を返します。
example.js
const nums = [3, 1, 2];
const returned = nums.sort((a, b) => a - b);
console.log(nums);
console.log(returned === nums);
出力書き換えて実行できます
[ 1, 2, 3 ]
true
example.js
console.log([10, 9, 1, 100].sort());
console.log([10, 9, 1, 100].sort((a, b) => a - b));
出力書き換えて実行できます
[ 1, 10, 100, 9 ]
[ 1, 9, 10, 100 ]
戻り値は新しい配列ではありません。元と同じものです。
比較関数を渡さないと文字列になる
引数を省くと、要素を文字列に変えてから並べます。
example.js
console.log([true, false, null, 10, 'a'].sort());
出力書き換えて実行できます
[ 10, 'a', false, null, true ]
example.js
const nums = [3, 1, 2];
console.log([...nums].sort((a, b) => b - a));
console.log([...nums].sort(() => 0));
出力書き換えて実行できます
[ 3, 2, 1 ]
[ 3, 1, 2 ]
'10' < '9' なので、10が9より前に来ます。
数値を並べるときは、必ず比較関数を渡してください。
型が混ざっていても同じです。
example.js
const nums = [3, 1, 2];
console.log(nums.toSorted((a, b) => a - b));
console.log(nums);
出力書き換えて実行できます
[ 1, 2, 3 ]
[ 3, 1, 2 ]
example.js
const nums = [3, 1, 2];
console.log([...nums].sort((a, b) => a - b));
console.log(nums);
出力書き換えて実行できます
[ 1, 2, 3 ]
[ 3, 1, 2 ]
'10' < 'a' < 'false' < 'null' < 'true' の順です。
中身が何であれ、文字列として比べられます。
→ 配列を数値の大きさで並び替える
比較関数が返すべきもの
compare(a, b) は数値を返します。
| 戻り値 |
意味 |
| 負の数 |
a を前に |
| 0 |
順番を変えない |
| 正の数 |
b を前に |
だから a - b で昇順、b - a で降順になります。
example.js
const nums = [1, 2, 3];
console.log(nums.toReversed(), nums);
出力書き換えて実行できます
[ 3, 2, 1 ] [ 1, 2, 3 ]
example.js
const rows = [
{ name: 'A', group: 2 },
{ name: 'B', group: 1 },
{ name: 'C', group: 2 },
{ name: 'D', group: 1 },
];
console.log(rows.toSorted((x, y) => x.group - y.group).map((r) => r.name));
出力書き換えて実行できます
[ 'B', 'D', 'A', 'C' ]
常に0を返せば、何も動きません。
元を変えたくないなら
toSorted() は新しい配列を返します(ES2023)。
example.js
const rows = [
{ g: 2, n: 'b' }, { g: 1, n: 'z' }, { g: 2, n: 'a' }, { g: 1, n: 'y' },
];
console.log(rows.toSorted((x, y) => x.g - y.g || x.n.localeCompare(y.n)).map((r) => r.g + r.n));
出力書き換えて実行できます
[ '1y', '1z', '2a', '2b' ]
example.js
console.log([3, undefined, 1].sort((a, b) => a - b));
console.log([3, , 1].sort());
出力書き換えて実行できます
[ 1, 3, undefined ]
[ 1, 3, <1 empty item> ]
古い環境では、複製してから並べます。
javascript
const nums = [5, 3, 9, 1, 7, 2, 8];
console.log(nums.sort((a, b) => a > b));
[ 5, 3, 9, 1, 7, 2, 8 ]
javascript
console.log(['い', 'あ', 'う'].sort((a, b) => a - b));
[ 'い', 'あ', 'う' ]
toSorted() は Node 20 / Chrome 110 / Safari 16 より前では使えません。
reverse() も同じく破壊的で、toReversed() が対になります。
example.js
console.log(['い', 'あ', 'う'].sort());
console.log(['banana', 'Apple', 'cherry'].sort((a, b) => a.localeCompare(b, 'ja')));
出力書き換えて実行できます
[ 'あ', 'い', 'う' ]
[ 'Apple', 'banana', 'cherry' ]
javascript
const original = [3, 1, 2];
const sorted = original.sort((a, b) => a - b);
console.log(original);
[ 1, 2, 3 ]
同じ値の順番は保たれる
sort() は安定ソートです(ES2019 以降、仕様で保証されています)。
比較で同じと判定された要素は、元の順番のままになります。
group が1の B と D、2の A と C。
それぞれ元の並び順が保たれています。
だから「まず名前順、次に点数順」のような並べ替えは、逆の順に2回かけても作れます。
ただし、1回で書くほうが読みやすくなります。
|| は左が 0 のときだけ右を見ます。同点のときの2番目の基準を、そのまま書けます。
undefined と穴は必ず最後
undefined は比較関数に渡されません。無条件で末尾へ回されます。
穴はさらにその後ろです。
比較関数の中で undefined を気にする必要はありません。 来ないからです。
よくある間違い
比較関数が真偽値を返している
まったく並んでいません。
a > b は true / false を返します。数値に直すと1と0で、
「b を前に」と「変えない」しか表せません。「a を前に」(負の数)が作れないので、並べ替えが成立しません。
要素数が少ないと、たまたま並ぶことがあります。だから見逃されがちです。
a - b を使ってください。
文字列を a - b で比べる
文字列の引き算は NaN です。NaN は0と同じ扱いになるので、何も起きません。
文字列は既定の並べ替えか、localeCompare() を使います。
→ 文字を正しく数える
並べ替えたつもりで元が変わっている
sorted を作ったつもりでも、original も並び替わっています。同じ配列だからです。
元を残したいなら toSorted() か [...original].sort() を使ってください。
まとめ
sort() は元を書き換える。戻り値は同じ配列
- 比較関数を省くと文字列として並ぶ。数値には必ず渡す
- 比較関数は数値を返す。真偽値では並ばない
undefined と穴は比較関数に渡されず、必ず末尾
- 安定ソートが保証されている。同点は元の順のまま
- 元を残すなら
toSorted() か [...arr].sort()