寄せられたPerl Quizの回答と解説
Perl Quiz に挑戦して負けました(追記有り) - だるろぐ
の。軽い気持ちで聞いてみたら大変な事に。どうしてこうなった。
おさらい。
もっとがんばりましょう
俺。
http://gist.github.com/337522
my $a_ref2 = \$a_ref; $a_ref2 = \$$a_ref2->{$_} for split /\./, $key; $$a_ref2 = $value;
1行に出来ませんでした。
$keyをドットで分解できる限りし続け、値の格納されているアドレスを追い、分解できなくなったら値をアドレスに直接ぶちこんで終わり。
これしか思い付かず。
ここからプログラマ。
よくできました
kamipoさんの解答
http://twitter.com/kamipo/status/10724497868
eval join('->', '$a_ref', map "{$_}", split /\./, $key) . ' = $value';
$keyの続く限り分解して(ドットで)、evalして、ハッシュリファレンスを追い続け、最終的に $a_ref を作り出す。
アルゴリズムは多少変われど、これが最も一般的な解答。模範解答。
ここから変態注意。
へんたいよくできました
kamipoさんの解答その2
http://twitter.com/kamipo/status/10726465022
ref(($b ||= $a_ref)->{$_}) ? ($b = $b->{$_}) : ($b->{$_} = $value) for split /\./, $key;
ここで必要な知識。
「$a/$bは use strict; 下でもmy宣言しなくても使える」
http://twitter.com/ktat/status/10726848346
% perldoc perlvar $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.
訳。
http://perldoc.jp/docs/perl/5.10.0/perlvar.pod
この特殊性により、$a と $b は、たとえ strict 'vars' を使っているときでも (use vars や our() を使って) 宣言する必要がありません。
sort() を使うときに$a/$bが使えるのは知ってたけど、そもそも何処でも使えたようです。
解説
- $keyをsplit出来る限り、以下をループ。
「$bの中身書き換えただけで何で$a_refまで変わるんだyp!」と思ったけどリファレンスコピーしたらコピー先の値を変更したらコピー元の値もそりゃ変更されますね。
何で$bなんて使うのかと言えば、そりゃ「できれば1行」という条件を満たすため。my()なんて使ったら即失格なので、特殊変数を使ってる。
ktatさんの解答
http://twitter.com/ktat/status/10726279122
$a_ref->{$_} and ref $a_ref->{$_} ? foo($a_ref->{$_}, $key, $value) : ($a_ref->{$_} = $value) for split /\./, $key;
解説
ktatさんの解答その2
http://twitter.com/ktat/status/10728205141
ref $a_ref->{$1} ? foo($a_ref->{$1}, $key, $value) : ($a_ref->{$1} = $value) if $key =~s{^(\w+)\.?}{};
さっきのは無駄に回ってたらしい。でもdo {warn 1} とかで見てもどっちも3回しか回ってないように見えるよボスケテ。
解説
さっきとほぼ同じなので相違点だけ。
- まずは最後のif文を評価する。これ重要。
- s///による置換が成功すれば、$keyに置換個所代入。最初は"foo"。
- $a_ref->{foo} を使ってfoo再帰。終わり。
最初に見たときは「何で$1がいきなり真なんだyp!」と思ったけど、三項演算子全体よりも、後置if文の方が先に評価されるから、そりゃそうだ。
スコープが見えていない。
( ref $a_ref->{$1} ? foo($a_ref->{$1}, $key, $value) : ($a_ref->{$1} = $value) ) if $key =~s{^(\w+)\.?}{};
というわけですね。そして特殊変数の$1を使って再帰でも値を共有させている。
nabokov7さんの解答
http://twitter.com/nabokov7/status/10730694327
for(my @k=split(/\./,$key) and my $p=$a_ref;scalar @k>1 or ($p->{$k[0]}=$value and 0);$p=$p->{shift(@k)} ){};
「1行」の定義が覆される。確かに1行なのだけど、これはいいんだろうかw
解説
中身が空のfor文。forの条件定義部分でループ。
- $keyをsplitし、@kを宣言しつつ代入。真。andに続き、$pを宣言し、$a_refをコピー。
書いてて意味が通じるわけが無いと思った。
ので挙動を書こう。
- @kが('foo', 'bar', 'baz')で宣言される
- $p変数を宣言しつつ、$a_refの値を代入。
- scalar @k>1を評価。真。ループ部分突入。
- $p = $p->{foo} を評価。@kは('bar', 'baz')になる。ループ部分脱出。
- scalar @k>1を評価。真。ループ部分突入。
- $p = $p->{bar} を評価。@kは('baz')になる。ループ部分脱出。
- scalar @k>1を評価。偽。ループ条件の右辺、つまりorの右辺を評価。
- $p->{baz}=$valueを評価。真。andの右辺評価。
- しかし右辺にあるのはただの数値リテラル0。問答無用で偽。
- for文終了。
言葉も出ない。
nabokov7さんの解答その2
http://twitter.com/nabokov7/status/10730770168
strictが無いならこうするとのこと。
&{$s=sub{my($v,$x,@r)=@_;(scalar @r>1) ? $s->($v->{shift @r},$x, @r) : ($v->{$r[0]}=$x) } }($a_ref,$value,split(/\./,$key));
先ほどのfor文を、無名関数を宣言して即実行し、そして自分自身を宣言中に自分自身を呼び出すように変更したもの。
そろそろ暴力的。しかし先ほどのよりは分かりやすい(はずだ)。
nabokov7さんの解答その3
http://twitter.com/nabokov7/status/10731331836
&{$main::s=sub{($#_>2)?$main::s->($_[0]->{$_[2]},$_[1], @_[3..$#_]):($_[0]->{$_[2]}=$_[1])}}($a_ref,$value,split(/\./,$key));
ここで必要な知識。
「パッケージ名で修飾すれば use strict 下でも宣言が要らない」
例えばmainパッケージ内なら $main::hoge = 1; とか書けば、$hogeを扱える。
先ほどのが、 $main::s と変わった。変数名も大幅省略。記号プログラミングに近づく。
三項演算子の条件部分、 ($#_>2) が真なら $#_ は少なくとも3以上なので、アレイスライスを使用し、 @_[3..$#_] と書いているところが汎用性に欠けるか。
egawataさんの解答
http://twitter.com/egawata/status/10732123137
${(map{$a=$a?\${$a}->{$_}:\$a_ref->{$_}}split/\./,$key)[-1]}=$value;
アドレス使用。そして余裕で$a使用。
どっから言えばいいのやら。
- map実行。ループ条件は、$keyがsplitで回せる間。
- $a評価。偽。$a_ref->{foo}の値のアドレスを返し、それは$aに代入される。
- $a評価。真。$a_ref->{bar}の値のアドレスを返し、それは$aに代入される。
- $a評価。真。$a_ref->{baz}の値のアドレスを返し、それは$aに代入される。
- というループの結果、(\$a_ref->{foo}, \$a_ref->{foo}->{bar}, \$a_ref->{foo}->{bar}->{baz}) という配列が出来上がる。
- その配列の[-1]番目、つまり最後の要素に$valueを代入。アドレスに値を代入してるので$a_ref->{foo}->{bar}->{baz}も書き換わる。
ywataseさんの解答
http://twitter.com/ywatase/status/10732280287
$key =~/\./ ? foo($a_ref->{$`}, $', $value) : ($a_ref->{$key} = $value);
年に3回くらいしか使わない特殊変数を使われたのでperldoc。
説明しようとしたけどええいコード書いた方が分かりやすいだろ!
"hoge-foo-bar" =~ /foo/; say $`; #=> hoge- say $'; #=> -bar
今までの再帰に同じ。
ywataseさんの解答その2
$key=~s/\./}->{/g ? eval"\$a_ref->{$key} = $value" : ($a_ref->{$key} = $value);
何回も思ったけど皆の解答が文字化けにしか見えないんだ。
- $keyを置換。 . を }->{ に一括置換して、リファレンスを探索して、$a_ref->{foo}->{bar}->{baz}のアドレスに$value代入。
今までのevalの中で最もスマート。
汎用性は下がるけど、こうでもいける。
$key=~s/\./}->{/g and eval"\$a_ref->{$key} = $value";
ywataseさんの解答その3
use English; # use のところに書いてください $key =~/\./ ? foo($a_ref->{$PREMATCH}, $POSTMATCH, $value) : ($a_ref->{$key} = $value);
「読めない」っつったら「じゃあ見やすく書き直す」って言われてこれがきた。
俺が見づらいっつったのはeval版だったりするけど!
終わり
というわけで、解説終わり。何回か泣きそうになったのは秘密です。
金曜の夜と土曜の昼をこんな事に使ってとても疲れました。でも楽しくて仕方なかった。
プログラムちょー楽しい!プログラマ凄いな!!皆さんありがとうございました!