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 の比較式を 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 ならではのテクニックだったらしい。