配列(9) sort, reverse, join

なんだか三日連続で配列のお勉強をしている気がするので(笑)、配列はこのへんでそろそろ終わらせます。でも、おかげで配列操作がよくわかりました。
sort は、配列中の要素を昇順ソートした配列を返します。渡した配列を直接ソートするわけではありません。(非破壊的)
reverse は、配列中の要素の並び順を逆にした(昇順ソートではない)配列を返します。こちらも直接配列を操作するわけではないので、非破壊的な関数です。
また、結果表示のために配列中の要素を指定した文字列でつなげた文字列を返す join を使ってみました。

# arrtest10.pl
my @arr = (11, 7, 3, 13, 5, 2);
print "arr:      ", join(", ", @arr), "\n";
 
my @sorted = sort(@arr);
print "sorted:   ", join(", ", @sorted), "\n";
print "arr:      ", join(", ", @arr), "\n";
 
my @reversed = reverse(@arr);
print "reversed: ", join(", ", @reversed), "\n";
print "arr:      ", join(", ", @arr), "\n";

今回は、大小がわかりやすい整数の配列を使ってみました。
元の配列が変更されていない事を確かめるために、sort, reverse 関数を呼んだ後に表示させています。
以下が実行結果です。

D:\dev\perl>perl arrtest10.pl
arr:      11, 7, 3, 13, 5, 2
sorted:   11, 13, 2, 3, 5, 7
arr:      11, 7, 3, 13, 5, 2
reversed: 2, 5, 13, 3, 7, 11
arr:      11, 7, 3, 13, 5, 2

あれ? sorted(ソート結果)がおかしいですね。ちゃんとソートされていません。reverse は上手くいっているのですが……。
よくよく見ると、最初に「1」が付いている数字が来てから、その後に「2」「3」...と、小さい順にならんでいます。どうやら、「文字列」として辞書順ソートされているようです。
そこで、文字列の配列をソートしてみました。

# arrtest11.pl
my @arr = ("abc", "aac", "bbc", "acb", "cab");
print join("\n", sort(@arr)), "\n";

ソート結果を"\n"(改行)
実行結果です。

D:\dev\perl>perl arrtest11.pl
aac
abc
acb
bbc
cab

辞書順にソートされている事がよくわかります。
では、数字を「数字として」ソートするにはどうしたらいいんでしょうか。と、「perldoc -f sort」をよく読んでみました。

    sort SUBNAME LIST
    sort BLOCK LIST
    sort LIST
            In list context, this sorts the LIST and returns the sorted list
            value. In scalar context, the behaviour of "sort()" is
            undefined.

            If SUBNAME or BLOCK is omitted, "sort"s in standard string
            comparison order. If SUBNAME is specified, it gives the name of
            a subroutine that returns an integer less than, equal to, or
            greater than 0, depending on how the elements of the list are to
            be ordered. (The "<=>" and "cmp" operators are extremely useful
            in such routines.) SUBNAME may be a scalar variable name
            (unsubscripted), in which case the value provides the name of
            (or a reference to) the actual subroutine to use. In place of a
            SUBNAME, you can provide a BLOCK as an anonymous, in-line sort
            subroutine.

英文ですが要約すると「ソートの細かい挙動を指定するコードを指定できる」と書いてあります。コードの渡し方は SUBNAME(サブルーチン名?)か BLOCK(コードブロック)を渡せる、との事です。(比較結果を「負数」「0」「正数」のどれかで返す)
省略した場合は「標準的な文字列比較順」と書いてありますから、先ほど整数の配列を渡した時も文字列比較された事がわかります。
また、例文は以下のようになっています。

                # sort lexically
                @articles = sort @files;

                # same thing, but with explicit sort routine
                @articles = sort {$a cmp $b} @files;

                (中略)

                # sort numerically ascending
                @articles = sort {$a <=> $b} @files;

つまり、{$a cmp $b} や {$a <=> $b} がコードブロックにあたるわけですね。演算子や関数については、まだちゃんと勉強していませんが、「cmp」と「<=>」が2項を比較して「負数」「0」「正数」のどれかを返す演算子である事はなんとなく想像できます。
$a と $b については、先日の日記に親切な通りすがりさんから頂いたコメントで勉強しました。ありがとうございました♪
頂いたコメントを以下に引用させて頂きます。

通りすがり 『気になることがあったので1つだけ。$aと$bは特別な場合をのぞいて使わないほうがいいです。sort関数で比較する場合に使うので、普段は使わないようにしたほうがいいですよ。』

そこで僕は、こういった魔法のスカラ変数を解説している「perldoc perlvar」を読んで $a と $b に関する説明を見つけました。

    $a
    $b      Special package variables when using sort(), see "sort" in
            perlfunc. Because of this specialness $a and $b don't need to be
            declared (using use vars, or our()) even when using the "strict
            'vars'" pragma. Don't lexicalize them with "my $a" or "my $b" if
            you want to be able to use them in the sort() comparison block
            or function.

和訳すると、「$a と $b は sort 関数用の特別な変数です。その特別性から、$a と $b は宣言する必要がありません。」と書かれています。その後の説明は恐らく変数のスコープに関する記述だと思いますが、勉強中です。(-_-;
とにかく、ソート関数に渡すコードの中では $a と $b を比較すれば良いことはわかりました。この比較関数を何度も呼ぶ事で sort 関数が自動的にソートしてくれるのでしょう。
あとは、「cmp」と「<=>」演算子の違いについては例文からもわかりましたが、一応「perldoc perlop」で調べました。

    Binary "<=>" returns -1, 0, or 1 depending on whether the left argument
    is numerically less than, equal to, or greater than the right argument.
    Binary "cmp" returns -1, 0, or 1 depending on whether the left argument
    is stringwise less than, equal to, or greater than the right argument.

「数字として比べる(numerically)」か、「文字列として比べる(stringwise)」かの違いですね。つまり、「<=>」を使えば数字をちゃんとソートできそうです。

# arrtest12.pl
my @arr = (11, 7, 3, 13, 5, 2);
 
my @sorted = sort { $a <=> $b; } @arr;
print join(", ", @sorted), "\n";

実行結果です。

D:\dev\perl>perl arrtest12.pl
2, 3, 5, 7, 11, 13

数字としてソートできました。(^_^)