🐥

JavaScriptで数値を含む文字列を自然順ソートする方法(外部ライブラリ不要)

に公開

JavaScriptで配列をソートするとき、sort()メソッドで単純に文字列ソートをしてしまうと、次のような問題に遭遇することがあります。

const arr = ['10A', '9A', '2B', '11A', '2A'];
arr.sort();
console.log(arr);
// ['10A', '11A', '2A', '2B', '9A']

文字列としてソートされるため、'10A''11A' は先頭から見た「文字のコード値」の順番で '2A' より前に来てしまい、本来の数値の大小関係(2 < 9 < 10 < 11)とは異なる並びになってしまいます。

そこで本記事では、外部ライブラリを使わずに JavaScript 標準 API だけで「数値を考慮したソート」を行う方法をご紹介します。


なぜ「10A」が「2A」より先に来るのか? ~ 文字コード比較の仕組み ~

JavaScript の sort() は、デフォルトでは要素を文字列として比較します。文字列の比較は「先頭の文字から順番に、文字コード(文字を数値に割り当てたもの)を比べて、どちらが大きいかを決定する」という方式で行われます。

たとえば、ASCII コードの一部は次のように定義されています。

文字 コード値
'0' 48
'1' 49
'2' 50
'9' 57
'A' 65
'a' 97

'10A''2A' を比較すると、

  1. '10A' の先頭文字 '1' (コード値: 49)
  2. '2A' の先頭文字 '2' (コード値: 50)

まず '1''2' を比べると 49 < 50 なので、'1' のほうが小さい という判定になり、結果として文字列ソートでは '10A' < '2A' とみなされます。
数値としては 10 > 2 なのに、文字列としては '10A' が先に来るのが、この文字コードによる比較が原因です。


解決策:localeCompare + { numeric: true } を使う

実は JavaScript 標準の localeCompare メソッドを使い、オプションで { numeric: true } を指定することで、自然順ソート(numeric sort) が実現できます。
ECMAScript Internationalization API (Intl) を活用したこの機能は、ブラウザでも Node.js でも広くサポートされており、外部ライブラリなしで十分に利用できます。

サンプルコード

// 比較関数を定義
function compareWithNumeric(a, b) {
  // 第2引数の locale には undefined を指定し、
  // options で numeric: true を設定
  return a.localeCompare(b, undefined, {
    numeric: true,
    sensitivity: 'base',
  });
}

// テスト用の配列
const arr = ['10A', '9A', '2B', '11A', '2A'];

// sort時に比較関数を渡す
arr.sort(compareWithNumeric);

console.log(arr);
// ["2A", "2B", "9A", "10A", "11A"]

実行すると、'10A''9A' の後ろに来ており、数字として 9 < 10 が考慮された形でソートされています。

// 数値が文字列の後に来る場合
> ["fig. 11", "fig. 1", "fig. 2"].sort(compareWithNumeric)
// [ 'fig. 1', 'fig. 2', 'fig. 11' ]

また、数値が文字列の後に来る場合も上記のように自然な順にソートしてくれます。

オプション解説

  • numeric: true
    これを指定することで、文字列中の数字を数値として解釈し、ソートしてくれます。
  • sensitivity: 'base'
    'A''a' などの大文字・小文字の違いや、アクセント付き文字をどの程度区別するかを指定するオプションです。日本語の場合は大半がそのままで問題ないため、だいたい baseaccent を使うことが多いでしょう。

Intl.Collator を使った方法

もう少しオブジェクト指向的に使いたい場合は、Intl.Collator オブジェクトを生成してから、そこに用意されている .compare() メソッドを渡す方法もあります。

// Collatorオブジェクトを生成
const collator = new Intl.Collator(undefined, {
  numeric: true,
  sensitivity: 'base',
});

const arr = ['10A', '9A', '2B', '11A', '2A'];

// compareメソッドをソートに渡す
arr.sort(collator.compare);

console.log(arr);
// ["2A", "2B", "9A", "10A", "11A"]

こちらも処理内容は同じで、numeric: true を指定することで「数値を考慮したソート」が可能になります。


なぜ外部ライブラリ無しでも十分なのか?

自然順ソートを実現するライブラリとしては、natsort などがよく知られています。確かに非常に便利で歴史も長いのですが、実は現行のブラウザ・Node.js では標準APIで十分に自然順ソートが可能です。

  • メリット:

    • 追加の依存関係が増えず、ビルドサイズが膨らまない
    • ライブラリの更新やメンテナンスを気にせずに済む
  • デメリット:

ほとんどの場合、モダン環境(比較的新しいブラウザや Node.js)であれば標準APIで十分でしょう。


まとめ

  • JavaScript の Array#sort() はデフォルトで文字列(コード値)の順序でソートする
  • 数値を含む文字列を自然順にソートしたいときは、localeCompare または Intl.Collatornumeric: true が簡単でおすすめ
  • サポート状況によっては、natsort などの専用ライブラリを使う選択肢もある

最近のブラウザや Node.js 環境では Intl.Collator による自然順ソートがほぼサポートされているので、まずは標準APIを使ってみて、問題があればライブラリの利用を検討すると良いでしょう。


おわりに

外部ライブラリは非常に便利ですが、まずは標準APIの機能を知って使いこなすことが大切です。今回紹介した localeCompare + numeric: true はとてもシンプルなので、数値を含む文字列のソートで「なんか順番がおかしい…」と思ったら、ぜひ試してみてください!

もしこの記事がお役に立ったら、ぜひいいねシェアをお願いします。
ご質問・フィードバックなどございましたら、コメント欄やSNSで気軽にお知らせください。

Discussion