Hatena::Diary

hnwの日記 このページをアンテナに追加 RSSフィード

[プロフィール]

2009年2月27日(金) PHPのsort関数は相当おかしい このエントリーを含むブックマーク このエントリーのブックマークコメント

オヤジギャグがこらえられなくなったら立派なオヤジだと思います。それはさておき、今日は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ボトルネックになってしまうので、そうも言っていられないんでしょうね…。

ページビュー
146175