動機
シミュレーションするとき、何種類かのパラメータをいくつか変えて行いたいときがよくある。
$ ./sim --num 300,400,500,600 -ber 1e-5,1e-4,1e-3
今まで何も考えず、簡単化するとこんな感じのスクリプトを組んでたのだけれど、パラメータが多くなってくるとfor文のネストがいちじるしく多くなってぶっころしたくなった。for文はもうおなかいっぱい。みんなどうやってるんだろう。
foreach my $num (@num) { foreach my $ber (@ber) { system( './sim-written-another-lang $num $ber' ) for (1..$cnt) } }
for文をつくらないようにするにはどうしたらいいんだろうと考えて、シミュレーションパラメータは全部ハッシュにして渡すことにした。こんな感じのハッシュをやまほどつくる。
$p1 = { num => 300, ber => 1e-5, frate => 0.001, # default failure rate }; $p2 = { num => 400, ber => 1e-5, frate => 0.001, # default failure rate };
で、そういうハッシュでパラメータを関数に渡すとfor文がネストせずに幸せ。
exe_sim( $_ ) for ($p1, $p2);
問題
というわけで問題は、こういう感じのarrayrefを含むハッシュから、arrayrefを含まない複数のhashref配列を山ほどこさえて、幸せになるためにはどうしたらいいか。
%paramset = ( num => [300,400,500,600], ber => [1e-5,1e-4,1e-3], frate => 1e-5, );
書いてみた
arrayrefだけとりあえず展開してみる。deep copyしたのは、shallow copyだと単にdumpしたとき気持ち悪かったからであって、あんま意味はないかもしれない。ちなみに、汎用性はない悪寒がする。
sub expand_hash { my (@hashes) = @_; my ($original, @newhash) = (0); my @newhash; foreach my $href (@hashes) { my $expand = 0; for my $key ( keys %$href ) { if( ref( $href->{$key} ) eq 'ARRAY' ){ push @newhash, map { { %{ dclone( $href ) }, $key => $_ } } @{ $href->{$key} }; $expand = 1; last; } } unless( $expand ) { push @newhash, $href; $original++; } } return $original == @hashes? @hashes : expand_hash( @newhash ); }
実験
use Data::Dumper; my @hashrefs = expand_hash( { a => [1,2,3], b => 3, c => [4,5] } ); print Dumper( \@hashrefs );
実験結果
忌むべき車輪の再発明のような気がしないでもない。
$VAR1 = [ { 'c' => 4, 'a' => 1, 'b' => 3 }, { 'c' => 4, 'a' => 2, 'b' => 3 }, { 'c' => 4, 'a' => 3, 'b' => 3 }, { 'c' => 5, 'a' => 1, 'b' => 3 }, { 'c' => 5, 'a' => 2, 'b' => 3 }, { 'c' => 5, 'a' => 3, 'b' => 3 } ];
オチ
ていうかこれって単に順列の応用であって、ほぼ間違いなく(しかも性能のきわめて悪い)車輪の再発明でしょうほんとうにあ(ry