Seaside Laboratory

Post

Perl での複数キーによるソートと論理演算の仕様

表形式の配列を複数キーでソートする方法を探していたら以下のようなやり方を見つけた。

# 表データの準備
@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 だと何でうまくいくのか不思議だったのでドキュメントを参照してみると、

|| 演算子と && 演算子は、単に 0 や 1 を返すのではなく、
最後に評価された値を 返すという点において、C と違っています。

と書いてあったので、Perl ならではのテクニックだったらしい。