simanのブログ

ゆるふわプログラマー。競技プログラミングやってます。Ruby好き

「Code Festival A If you cannot solve this」参加記録

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ぐらい使った気がします。