PHPのsort関数は相当おかしい
追記(2009/02/28 15:35):ソートする配列の要素が数値または数値形式の文字列のみの場合は、<、==、>が推移律を満たすので、この記事のような矛盾は起こりません。念のため。
オヤジギャグがこらえられなくなったら立派なオヤジだと思います。それはさておき、今日はPHPのsort関数が不思議な挙動をする例を紹介します。
sort関数の紹介
sort ― 配列をソートする
説明
bool sort ( array &$array [, int $sort_flags= SORT_REGULAR ] )この関数は配列をソートします。この関数が正常に終了すると、 各要素は低位から高位へ並べ替えられます。
マニュアルをみる限り普通のソート関数です。省略可能な2番目の引数の意味は次の通りです。
sort_flags
オプションの 2 番目のパラメータ sort_flags は、以下の値によりソートの動作を修正するために使用することが可能です。
ソート型のフラグ:
sortの何が悪いのか
マニュアルを見ただけで変な点に気づく人はかなり少数なのではないでしょうか。では、この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の日記」で指摘していますので参考にしてください。