reduce だいすき

基本

デミグラスソース使った料理が食べたい cho45 です。Perl といえば某MMOゲームと同時に起動できないプログラムとして有名ですが今回はそれとは関係ない話です。

Ruby 厨の多くが inject 厨である気がします (てきとーです) が、 Perl で List::Util::reduce を使っているところをあんまり見たことがないのでいくつか便利な例を紹介します。

reduce は何かというとリストを1つの値に纏めるものです。例えばリストの要素の合計は

use Perl6::Say;
use List::Util qw/reduce/;

my $list = [1, 2, 3, 4, 5];

say reduce { say "$a,$b"; $a + $b } @$list;
1,2
3,3
6,4
10,5
15

前回のループの返り値が $a に入り、$b には残りの要素のうち1つが入ります。

実は List::Util の提供する関数たちの殆どは reduce を使って実装されています。 例えば List::Util::sum という関数はまさに例に出したコードそのものですし、min, max も前回の値と比較をして現在の要素が小さいか多いきいか比較しているだけです。 まるまるコピペしてみると

# List::Util.pm

sub sum (@) { reduce { $a + $b } @_ }

sub min (@) { reduce { $a < $b ? $a : $b } @_ }

sub max (@) { reduce { $a > $b ? $a : $b } @_ }

sub minstr (@) { reduce { $a lt $b ? $a : $b } @_ }

sub maxstr (@) { reduce { $a gt $b ? $a : $b } @_ }

シンプルですね。

初期値指定

でもって、reduce は初期要素、つまり $a の最初の値を指定できます。 (というか Perl の場合は引数の渡し方の関係上、リストの最初の値と初期値の区別がないのですが)

use List::Util qw/reduce/;

use Data::Dumper;
sub p ($) { print Dumper shift }

my $data = [
        { name => "foo", value => 1 },
        { name => "bar", value => 2 },
        { name => "baz", value => 3 },
];

my $ret = reduce { +{ %$a, $b->{name} => $b } } {}, @$data;
p $ret;

#my $ret = reduce {
#       $a->{$b->{name}} = $b;
#       $a;
#} {}, @$data; # これも同じ
$VAR1 = {
          'bar' => {
                     'value' => 2,
                     'name' => 'bar'
                   },
          'baz' => {
                     'value' => 3,
                     'name' => 'baz'
                   },
          'foo' => {
                     'value' => 1,
                     'name' => 'foo'
                   }
        };

ハッシュの配列を name キーの値をキーにしたハッシュに変換するコードです。 reduce の第一引数にハッシュリファレンスを渡して、それを更新していく形で新しいハッシュを作ります。

+{} は度々話題になるハッシュリファレンスを明示するやつで、{ %$hasha, %$hashb } はハッシュを更新するイディオムです。

2番目の例も同じことをするコードですが、最後に $a を書かないといけないのがちょっとダサいところです。ただハッシュを作りなおさない分効率的かもしれません。

これを reduce を使わないで書こうとすると

my $ret = {};
for my $i (@$data) {
        $ret->{$i->{name}} = $i;
}
p $ret;

みたいな感じになりますね。 for ループと $ret 変数の関係がパっと見よくわからなく、 「$ret に空のハッシュリファレンスぃれてぇー、for ルゥプで $i に配列要素ぃれながらぁー、……」 みたいな感じで若干まどろっこしいのが残念です。 reduce を使えば 「$ret には reduce の結果をいれる。そしてそれは……」となるのでカッコイイです。

次は otsune さん。