CodeFestivalの短縮王の事前公開問題があったのでそれをやっていました。
anarchy golf - Code Festival A If you cannot solve this
問題としては
クラスメイトのテストの点が与えられる。 クラスの平均点の半分以下である点数を赤点と定義する。 s1が赤点ならFail 、赤点でなければ Passを出力する(s1とはクラスの1人目の人)
というものでした。そして、今回書いたコードはこれです。
#!ruby -apl $_=eval$F*'+~'+">-2*%d*%d?:Pass:'Fail'"%$F
ちょっとだけ解説します。
#!ruby -apl
この謎の記述ですが、これは「Rubyを実行オプション-a -p -lを有効にして実行するぞ!」という宣言です。
pオプションが有効になっている場合はプログラム全体が
while gets . . . print $_ end
のように動作します。ここで$_はgetsしたときの値が入っています。
aオプションはpオプションが有効になっている状態で各whileループで
$F = $_.split
を実行してくれます。
lオプションはpオプションが指定されているときに、各行に対してchop!を実行し、また$_の出力時に改行をつけてくれます。
他のオプションについては Rubyの起動 (Ruby 1.9.3) に詳しく書かれています。
今回は入力形式として以下のように与えられていました。
# クラスの人数 1人目の点数 2人目の点数 3人目の点数 4人目の点数 4 42 1 90 77
そのまま書くとこんな感じになるのですが
# クラスの人数と点数リストを分ける n,*a=$F.map(&:to_f) # 平均点の半分より大きければPass a[0]>a.inject(:+)/n/2?:Pass:'Fail'
これをベースにどんどん変化させます。まず
a[0]>a.inject(:+)/n/2
この部分ですが、to_fに依存しない形に変形します。(後々evalを使った式に変形するため)
2*n*a[0]>a.inject(:+)
次に数値化していた部分のコードをevalを使って省きます。
eval"2*#{$F[0]}*#{$F[1]}>#{$F[1..-1]*'+'}?:Pass:'Fail'"
このままだと長過ぎるので、String#%を使って左側の値は%dに置き換えます。
eval"2*%d*%d>#{$F[1..-1]*'+'}?:Pass:'Fail'"%$F
ここまで来ると「$F[1..-1]*'+'」の部分も消し去りたくなります。今はクラスの合計値を出すために
$F[1..-1]*'+' #=> 42+1+90+77
になっていますがこれを
$F*'+~' #=> 4+~42+~1+~90+~77
に置き換えます。この状態は、クラスの人数をn、合計点数をSとすると
n+(-S-n) => -S
とクラスの合計点数にマイナスをかけた値が出ます。これは「~n」が「~n == -n-1」を表すため、
n + ~s1 + ~s2 + ~s3 + ~s4 n + (-s1-1) + (-s2-1) + (-s3-1) + (-s4-1) n - (s1 + s2 + s3 + s4) - 4 -S
となり、ちょうどいい感じにnを消し去ることができます。
あとは、これを条件が崩れないように変形してあげると最後の
#!ruby -apl $_=eval$F*'+~'+">-2*%d*%d?:Pass:'Fail'"%$F
になります。「$F*'+~'」の記述をみつけるのに10hぐらい使った気がします。