デミグラスソース使った料理が食べたい 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 さん。