Seaside Laboratory

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