オヤジギャグがこらえられなくなったら立派なオヤジだと思います。それはさておき、今日はPHPのsort関数が不思議な挙動をする例を紹介します。
説明
bool sort ( array &$array [, int $sort_flags= SORT_REGULAR ] )
この関数は配列をソートします。この関数が正常に終了すると、 各要素は低位から高位へ並べ替えられます。
マニュアルをみる限り普通のソート関数です。省略可能な2番目の引数の意味は次の通りです。
sort_flags
オプションの 2 番目のパラメータ sort_flags は、以下の値によりソートの動作を修正するために使用することが可能です。
- SORT_REGULAR - 通常通りに項目を比較 (型は変更しません)
- SORT_NUMERIC - 数値的に項目を比較
- SORT_STRING - 文字列として項目を比較
- SORT_LOCALE_STRING - は、カレントのロケールに に基づき比較を行います。PHP 4.4.0 と PHP 5.0.2で追加されました。 PHP 6 より前のバージョンではシステムロケールを使用します。これは setlocale() を使用して変更可能です。 PHP 6 以降では、i18n_loc_set_default() 関数を 使用する必要があります。
マニュアルを見ただけで変な点に気づく人はかなり少数なのではないでしょうか。では、このsort関数が不思議な挙動を示す例を示します。
<?php $a=array("1e1", "1f1", "9"); sort($a); var_dump($a); $a=array("9", "1e1", "1f1"); sort($a); var_dump($a);
同じ3つの文字列要素を持つ、要素の出現順が異なる配列2つをソートしてみます。この結果は下記のようになります。
array(3) {
[0]=>
string(3) "1e1"
[1]=>
string(3) "1f1"
[2]=>
string(1) "9"
}
array(3) {
[0]=>
string(1) "9"
[1]=>
string(3) "1e1"
[2]=>
string(3) "1f1"
}
なんと、同じ要素を持つ配列をソートした結果が異なっています。
このような挙動となる原因は、下記のプログラムを動かしてみればわかります。
<?php var_dump("1e1"<"1f1"); // true var_dump("1f1"<"9"); // true var_dump("9" <"1e1"); // true
大小関係のはずなのに、じゃんけんのような関係が出来上がっています。なんでこんなキモいことが起こるかというと、3つめの不等式だけ数値として比較されているからです。PHPマニュアルの比較演算子の項にも書いてありますが、数値形式の文字列同士は数値として比較されるんです。
要するに、これはsortの第2引数のデフォルト値、SORT_REGULARがマトモじゃないってことです。sortの比較関数に推移律が成立しない演算子を使うと何が起こるかなんて、少し考えれば誰でもわかりそうなもんですよね。というか、世界中のどこかでトラブルが起きていそうな気がします。
sort関数を使うならSORT_NUMERICかSORT_STRING、適切な方を使ってください。マジで。
特に文字列のみをソートする場合には注意してください。SORT_STRINGの中身はstrcmpであり、推移律(A < B、B < CならA < C)は必ず成り立ちますので、SORT_REGULARのような矛盾は起こりません。
もちろん、様々な型の値をSORT_STRINGやSORT_NUMERICでソートする場合は、それぞれの値がどうキャストされるかについて注意が必要です。stringへのキャストで注意する点については、「PHPで==の代わりにstrcmp関数を使うことによる問題点 - hnwの日記」で指摘していますので参考にしてください。
PHPでsortするなよ!絶対にするなよ!
普通PHPで書くアプリだとsortはRDBがする仕事だろうからきっと平気だよね!と思いたいんですが、大規模サイトだとRDBがボトルネックになってしまうので、そうも言っていられないんでしょうね…。