仕事で画面に表示するデータの表示順がおかしいと報告を受け調査したところ、
下記のような感じの処理が問題だったと判明。
// 実際には外部から取得してきたデータの配列
// 末尾がシーケンス番号になっている文字列がバラバラの順番で配列に入ってる
$datas = [
'aaa_7',
'aaa_1',
'aaa_8',
'aaa_5',
'aaa_2',
'aaa_6',
'aaa_11',
'aaa_9',
'aaa_10',
'aaa_3',
'aaa_4',
];
usort($datas, function (string $a, string $b) {
return strcmp($a, $b);
});
上記の処理で実現したかったのは下記のように「配列内の値を末尾のシーケンス順に並び替える」
array(11) {
[0]=>
string(9) "aaa_1"
[1]=>
string(9) "aaa_2"
[2]=>
string(9) "aaa_3"
[3]=>
string(9) "aaa_4"
[4]=>
string(9) "aaa_5"
[5]=>
string(9) "aaa_6"
[6]=>
string(9) "aaa_7"
[7]=>
string(9) "aaa_8"
[8]=>
string(9) "aaa_9"
[9]=>
string(10) "aaa_10"
[10]=>
string(10) "aaa_11"
}
だったのですが実際には下記のような結果に
array(11) {
[0]=>
string(9) "aaa_1"
[1]=>
string(10) "aaa_10"
[2]=>
string(10) "aaa_11"
[3]=>
string(9) "aaa_2"
[4]=>
string(9) "aaa_3"
[5]=>
string(9) "aaa_4"
[6]=>
string(9) "aaa_5"
[7]=>
string(9) "aaa_6"
[8]=>
string(9) "aaa_7"
[9]=>
string(9) "aaa_8"
[10]=>
string(9) "aaa_9"
}
早速ですが上記のコードをどのように修正したかの結果だけ先に書いておきます
usort($datas, function (string $a, string $b) {
// strcmp()→strnatcmp()
return strnatcmp($a, $b);
});
strcmp()
をstrnatcmp()
に置き換えただけですね。
これで先ほどの配列内のデータを末尾のシーケンス順に並び替えることができました。
ではまず元々の処理がどのようなものだったかを解説していきますが、メインの処理はたった数行なので、ほぼ使用されている関数の説明です。
usort()
は配列の値をユーザー定義の関数でソートする関数です。
そしてそのusort()
のコールバック関数内で使用されているstrcmp()
は文字列を先頭から一文字ずつ比較していき、
同一ではないと判断された時点でその差を返す関数です。
文字列の比較はバイト列にて行い、同一ではないとわかった場合に返却する差はそのバイト列の差となっています。
上記の通り先頭から1文字ずつ比較していくため、aaa_10
とaaa_2
の比較時に1と2で比較されてしまい、aaa_10
が前でソートされてしまうという結果になりました。
そのため、比較部分の関数を自然順比較が可能なstrnatcmp()
に変更しています。
また、自然順という言葉があまり聞きなれない言葉だったためそちらも調べてみたのですが、wikipediaに寄ると「アルファベット順を基本とする,複数桁の数字が単一の文字として順序付けられるような照合規則」とのこと。
複数桁の数字が単一の文字として順序づけられたため、aaa_10
とaaa_2
の比較は10と2の比較になり、期待通りのソート結果となったということですね。