Posts
Perl で複数の項目を使ってソートする方法
表形式 (2 次元) の配列を複数の項目でソートする方法を探していたら以下のようなやり方を見つけた。
# 表データの準備 @rows = ( ['hoge', 3], ['fuga', 4], ['hoge', 1], ['moge', 9] ); # ユーザー定義ソート @rows = sort {$a->[0] cmp $b->[0] || $a->[1] <=> $b->[1]} @rows;
1 列目で比較して同じ内容だったら 2 列目でソートするというプログラム。比較関数内にある "||" がポイントで、1 列目の比較で同じだった場合 cmp は 0 を返すので偽と評価され、2 列目の比較結果が返されるという仕組み。
PHP で同じようなことをするときには以下のようなコードを書いていた。
// 表データの準備 $rows = array ( array('hoge', 3), array('fuga', 4), array('hoge', 1), array('moge', 9) ); // ユーザー定義ソート function cmp($a, $b) { $c = strcmp($a[0], $b[0]); if ($c == 0) { $c = $a[1] - $b[1]; } return $c; } usort($rows, 'cmp');
1 列目の比較結果を一時変数に入れて、同じだったときは 2 列目の比較結果で上書きしている。一時変数を使わないパターンだと以下のようになる。
function cmp($a, $b) { // strcmp を 2 度呼び出すので非効率 return strcmp($a[0], $b[0]) != 0 ? strcmp($a[0], $b[0]) : $a[1] - $b[1]; }
どちらの比較関数もイマイチなので Perl 的手法で簡潔に書いてみた。単純に Perl の比較式を PHP に変換しただけ。
function cmp($a, $b) { return strcmp($a[0], $b[0]) || $a[1] - $b[1]; }
実行してみるとソート結果が書き換え前と同じにならない。比較式にあやしさを感じたので以下のようなテストコードを書いた。
$a = (1 - 2) || (3 - 4); var_dump($a);
実行すると、
bool(true)
と出力されたので、原因は比較結果が boolean 型に変換されていることだった。何故、Perl だとうまくいくのか不思議だったので perldoc を読んでみると、
|| 演算子と && 演算子は、(C のように 単に 0 や 1 を返すのではなく) 最後に評価された値を返します。
と書いてあったので、Perl ならではのテクニックだったらしい。