PHP で、連想配列の配列を効率よくソートする
<?php $data = array( array('id'=>101, 'filename'=>'foo.jpg'), array('id'=>104, 'filename'=>'bar.jpg'), array('id'=>109, 'filename'=>'baz.jpg'), ); ?>
これをファイルサイズでソートしたいとする。
この場合、比較関数を定義して、その関数名をusort()に指定するのが一般的な方法である。
<?php // 比較関数 function compare_by_filesize($item1, $item2) { $size1 = filesize($item1['filename']); $size2 = filesize($item2['filename']); if ($size1 < $size2) return -1; if ($size1 > $size2) return 1; return 0; } // 比較関数を指定してソートする $sorted = $data; usort($sorted, 'compare_by_filesize'); var_export($sorted); ?>
しかし、これだと比較のたびにfilesize()が2回も呼び出され、効率が悪い。*1
そこで、まず最初にすべてのファイル名についてfilesize()を呼び出し、あらかじめファイルサイズを求めておく。そうしてからソートしたほうが、filesize()の呼び出しが大幅に少なくなるので、実行効率がいい。
<?php // 最初にファイルサイズを求めておく $sizes = array(); // キーはfilesize、値は$dataのindex foreach ($data as $i=>$item) { $size = filesize($item['filename']); $sizes[$size] = $i; } // ファイルサイズでソートする ksort($sizes); // キーの順番でソートする // ソートした順番で$dataから取りだす foreach ($sizes as $i) { $sorted[] = $data[$i]; } var_export($sorted); ?>
ポイントとなるのは、ksort() である。これは連想配列をキーの順番でソートする。例えば:
<?php $arr = array( 'foo'=>10, 'bar'=>20, 'baz'=>30, ); ksort($arr); var_export($arr); //// 結果: // array ( // 'bar' => 20, // 'baz' => 30, // 'foo' => 10, // ) ?>
PHPの連想配列は、Ruby1.8のHashやPythonのdictと違って、キーの順番が保存される。そのため、ksort()のような関数が用意されている。
ちなみに、上で紹介した方法は「シュワルツ変換(Schwartz Transform)」と呼ばれ、RubyやPythonではそれ用のメソッドや関数が用意されている。
## Ruby data = [ {'id'=>101, 'filename'=>'foo.jpg'}, {'id'=>104, 'filename'=>'bar.jpg'}, {'id'=>109, 'filename'=>'baz.jpg'}, ] sorted = data.sort_by {|item| File.size(item['filename']) } p sorted
## Python data = [ {'id':101, 'filename':'foo.jpg'}, {'id':104, 'filename':'bar.jpg'}, {'id':109, 'filename':'baz.jpg'}, ] from os.path import getsize sorted = sorted(data, key=lambda item: getsize(item['filename'])) print repr(sorted)
PHP では、そのものズバリという関数は用意されていないが、array_multisort() が使える。
<?php // 最初にファイルサイズを求めておく foreach ($data as $item) { $sizes[] = filesize($item['filename']); } $sorted = $data; // $sizesをソートするときに、その順番で$sortedも入れ替える array_multisort($sizes, $sorted); var_export($sorted); ?>
む、こっちのほうが簡単だったか。