だるろぐ

だるいぶろぐです

寄せられたPerl Quizの回答と解説

Perl Quiz に挑戦して負けました(追記有り) - だるろぐ
の。軽い気持ちで聞いてみたら大変な事に。どうしてこうなった。

おさらい。

条件

「できれば1行」
プログラマには「できれば」の文字は見えません。
尚、阿呆な解釈をすれば「1;2;3;4;も1行だよね!」となりますが、プログラマにはそんな解釈する人は居ません。
普通に考えれば「one sentence」ですね。後で覆されるんですが。


はい開始開始。
まずプログラマ失格な残念賞から。

もっとがんばりましょう

俺。
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が使えるのは知ってたけど、そもそも何処でも使えたようです。

解説
  1. $keyをsplit出来る限り、以下をループ。
    1. $b を評価する。未定義なので ||= の右辺を評価。
    2. $a_refを評価。真。$bに$a_ref代入。
    3. ref $b->{foo}を評価。$b は $a_ref。値は {bar => {baz => 1}} というハッシュレフなので HASH になり、真。三項演算子の真の方を評価。
    4. $bに$b->{foo}を代入。
    5. …ということを繰り返し、$b = $a_ref->{baz} まで進む。
    6. ref $b->{baz}は1なので、refの結果は偽。三項演算子の偽の方を評価。
    7. $b->{baz} = $value を評価して終わり。

「$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;
解説
  1. やっぱりsplitループ。以下を繰り返す。
    1. $a_ref->{foo}を評価。真。ref $a_ref->{foo}を評価。ハッシュレフなので真。三項演算子の真の方を評価。
    2. foo($a_ref->{foo}, $key, $value) としてfoo()を再帰
    3. というわけで3ループ目。foo({baz => 1}, $key, $value) で呼ばれる。
    4. ref $a_ref->{baz} が偽なので三項演算子の偽の方へ。
    5. $a_ref{baz} = $value; して終わり。
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回しか回ってないように見えるよボスケテ

解説

さっきとほぼ同じなので相違点だけ。

  1. まずは最後のif文を評価する。これ重要。
  2. s///による置換が成功すれば、$keyに置換個所代入。最初は"foo"。
  3. $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の条件定義部分でループ。

  1. $keyをsplitし、@kを宣言しつつ代入。真。andに続き、$pを宣言し、$a_refをコピー。
    1. ループ開始。条件は「『@kの要素数が1より大きい』か、『〔$valueが真〕と〔0」』が成立する間」。
      1. $pに$p->{shift @k)}を代入。

書いてて意味が通じるわけが無いと思った。
ので挙動を書こう。

  1. @kが('foo', 'bar', 'baz')で宣言される
  2. $p変数を宣言しつつ、$a_refの値を代入。
  3. scalar @k>1を評価。真。ループ部分突入。
  4. $p = $p->{foo} を評価。@kは('bar', 'baz')になる。ループ部分脱出。
  5. scalar @k>1を評価。真。ループ部分突入。
  6. $p = $p->{bar} を評価。@kは('baz')になる。ループ部分脱出。
  7. scalar @k>1を評価。偽。ループ条件の右辺、つまりorの右辺を評価。
  8. $p->{baz}=$valueを評価。真。andの右辺評価。
  9. しかし右辺にあるのはただの数値リテラル0。問答無用で偽。
  10. 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使用。
どっから言えばいいのやら。

  1. map実行。ループ条件は、$keyがsplitで回せる間。
  2. $a評価。偽。$a_ref->{foo}の値のアドレスを返し、それは$aに代入される。
  3. $a評価。真。$a_ref->{bar}の値のアドレスを返し、それは$aに代入される。
  4. $a評価。真。$a_ref->{baz}の値のアドレスを返し、それは$aに代入される。
  5. というループの結果、(\$a_ref->{foo}, \$a_ref->{foo}->{bar}, \$a_ref->{foo}->{bar}->{baz}) という配列が出来上がる。
  6. その配列の[-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);

何回も思ったけど皆の解答が文字化けにしか見えないんだ。

  1. $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版だったりするけど!

終わり

というわけで、解説終わり。何回か泣きそうになったのは秘密です。
金曜の夜と土曜の昼をこんな事に使ってとても疲れました。でも楽しくて仕方なかった。
プログラムちょー楽しい!プログラマ凄いな!!皆さんありがとうございました!