安定した `Array.prototype.sort`
犬の配列を持っているとして、それぞれの犬には名前と評価があります。(これが奇妙な例に聞こえるなら、これを専門としているTwitterアカウントがあることを知っておくと良いでしょう…聞かないでくださいね!)
// 配列が `name` でアルファベット順にソートされている点に注意してください。
const doggos = [
{ name: 'Abby', rating: 12 },
{ name: 'Bandit', rating: 13 },
{ name: 'Choco', rating: 14 },
{ name: 'Daisy', rating: 12 },
{ name: 'Elmo', rating: 12 },
{ name: 'Falco', rating: 13 },
{ name: 'Ghost', rating: 14 },
];
// 犬を `rating` の降順でソートします。
// (この操作は `doggos` を直接変更します。)
doggos.sort((a, b) => b.rating - a.rating);
配列は名前順(アルファベット順)であらかじめソートされています。代わりに評価順でソートするには(最高評価の犬が最初に来るように)、Array#sort
を使用し、評価を比較するカスタムコールバックを渡します。これが期待される結果です:
[
{ name: 'Choco', rating: 14 },
{ name: 'Ghost', rating: 14 },
{ name: 'Bandit', rating: 13 },
{ name: 'Falco', rating: 13 },
{ name: 'Abby', rating: 12 },
{ name: 'Daisy', rating: 12 },
{ name: 'Elmo', rating: 12 },
]
犬は評価順にソートされますが、それぞれの評価内では名前順(アルファベット順)にソートされています。たとえば、Choco と Ghost の評価はどちらも 14 ですが、ソート結果では Choco が Ghost の前に現れます。これは、元の配列でもこの順序だったためです。
しかし、この結果を得るには、JavaScript エンジンが 任意の ソートアルゴリズムを使用するわけにはいきません。いわゆる「安定ソート」が必要です。長い間、JavaScript の仕様では Array#sort
の安定性を要求せず、実装に任せていました。このため、この動作が未定義だったため、たとえば以下のようなソート結果も得られる可能性がありました。この場合、Ghost が突然 Choco よりも前に現れます:
[
{ name: 'Ghost', rating: 14 }, // 😢
{ name: 'Choco', rating: 14 }, // 😢
{ name: 'Bandit', rating: 13 },
{ name: 'Falco', rating: 13 },
{ name: 'Abby', rating: 12 },
{ name: 'Daisy', rating: 12 },
{ name: 'Elmo', rating: 12 },
]
つまり、JavaScript 開発者はソートの安定性に依存することができませんでした。実際、状況はさらに厄介でした。一部の JavaScript エンジンは、短い配列には安定ソートを使用し、大きな配列には不安定なソートを使用することがあったからです。これは非常に混乱を招きました。開発者はテストでは安定した結果を確認できても、本番環境で配列が少し大きくなると突然不安定な結果が得られることもありました。
しかし、良い知らせです。Array#sort
を安定ソートにする仕様変更を提案し、それが受け入れられました。現在、すべての主要な JavaScript エンジンは安定した Array#sort
を実装しています。これで JavaScript 開発者としてまたひとつ心配事が減りましたね。素晴らしい!
(あ、それと、TypedArray
にも同じ変更を行いました。こちらも安定ソートになっています。)
注: 仕様により安定性が要求されるようになりましたが、JavaScript エンジンは依然として任意のソートアルゴリズムを実装する自由があります。例えば、V8 は Timsort を使用しています。仕様は特定のソートアルゴリズムを義務付けていません。