hnwの日記

PHPのsort関数は相当おかしい


追記(2009/02/28 15:35):ソートする配列の要素が数値または数値形式の文字列のみの場合は、<、==、>が推移律を満たすので、この記事のような矛盾は起こりません。念のため。

オヤジギャグがこらえられなくなったら立派なオヤジだと思います。それはさておき、今日はPHPのsort関数が不思議な挙動をする例を紹介します。

sort関数の紹介

sort ― 配列をソートする

説明
bool sort ( array &$array [, int $sort_flags= SORT_REGULAR ] )

この関数は配列をソートします。この関数が正常に終了すると、 各要素は低位から高位へ並べ替えられます。

PHP: sort - Manual

マニュアルをみる限り普通のソート関数です。省略可能な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() 関数を 使用する必要があります。

PHP: sort - Manual

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の日記」で指摘していますので参考にしてください。

まとめ

PHPでsortするなよ!絶対にするなよ!

普通PHPで書くアプリだとsortはRDBがする仕事だろうからきっと平気だよね!と思いたいんですが、大規模サイトだとRDBボトルネックになってしまうので、そうも言っていられないんでしょうね…。