AtCoderで見かけたRubyショートコーディング術
先月くらいから、Rubyの勉強のためにAtCoder Beginner Contestの問題をちびちび解いています。
AtCoderでは、人の解答をコードの短い順にソート出来るのですが、
その中で見かけたショートコーディング術などを、適当に作った例題付きで紹介します。
※…テーマがショートコーディングなので、パフォーマンスについては考えないこととします(大して重い処理は登場しませんが…)
Rubyのバージョンは2.1.5です。
例題1
1個X円のリンゴをN個、Y円のミカンをM個購入した時の合計金額を求めよ。
入力は、
X N
Y M
の形式で行う。
- 入力例
3 5
2 6
- 出力例
27
解答
X,N=gets.chomp.split(' ').map{|n| n.to_i}
Y,M=gets.chomp.split(' ').map{|n| n.to_i}
puts X*N+Y*M
(100bytes)
パターンA ... 省いていい部分を削る
- to_iメソッドを呼んだときに、数字の後ろの改行文字は削除されるので、chompは不要。
- splitに引数を指定しなかった場合、デフォルトで
' '
をセパレータとするようになっているので指定する必要がない。 - to_iメソッドは、Procオブジェクトとしてmapに渡せる。
そんな感じで省いていった結果、次のようになりました。
X,N=gets.split.map &:to_i
Y,M=gets.split.map &:to_i
p X*N+Y*M
(65bytes)
procで繰り返しを省略(参考: class Proc)
g=proc{gets.split.map &:to_i}
X,N=g.call
Y,M=g.call
p X*N+Y*M
(62bytes)
evalで繰り返しを省略
X,N=eval s='gets.split.map &:to_i'
Y,M=eval s
p X*N+Y*M
(59bytes)
パターンB ... $< (ARGF) を使う
ARGVが空の時、ARGFに対してEnumerableのメソッドを呼ぶと、標準入力で受け取った内容を一行ずつ文字列として処理できる。(参考: object ARGF)
reduceで結果をまとめる
p $<.reduce(0){|r,s|r+s.split.map(&:to_i).reduce :*}
(55bytes)
適当な変数に格納して計算する
p $<.reduce(0){|r,s|a,b=s.split.map &:to_i;r+a*b}
(51bytes)
例題2
Qiita高校のある学年には、Nクラスの学級があり、各学級の男女の人数はそれぞれX(i), Y(i)となっている。
この時、1クラスあたりの平均人数を男女別で出力せよ。
ただし、小数点以下は切り捨てること。
入力は、
N
X(1) Y(1)
...
X(i) Y(i)
...
X(N) Y(N)
の形式で行う。
- 入力例
3
12 10
11 9
10 10
- 出力例
11
9
解答
x=y=0
(N=gets.to_i).times{a,b=gets.split.map &:to_i;x+=a;y+=b}
p x/N,y/N
(77bytes)
普通に書いたら、予想外に短くなっちゃいました…。
$<を使うともう少し短くなります。
x=y=0
N=gets.to_i
$<.map{|s|a,b=s.split.map &:to_i;x+=a;y+=b}
p x/N,y/N
(72bytes)
ワンライナーにする
puts (N=gets.to_i).times.map{gets.split.map &:to_i}.transpose.map{|a|a.reduce(:+)/N}
(86bytes)
- (1..N).map or N.times.map を使えば、N個のデータをそのまま配列に格納できる。
- transposeを使うと、配列の行と列を入れ替えられ、X,Yの配列に分けることが出来る。
- 配列をputsに渡すと、要素を一つずつ改行しながら出力する。
上記を利用して、先ほどの解答を一行にまとめましたが、
かえって長くなってしまいました…。つらい…。
例題3
Qiita高校にはN人の生徒がいます。
各生徒の身長X(i)の平均と、太郎君の身長Tを比較したとき、
T > X(ave) ならば tall
T < X(ave) ならば short
T = X(ave) ならば normal
と出力しなさい。
ただし、身長X(i)の平均の小数点以下は切り捨てること。
入力は、
N T
X(1)
...
X(i)
...
X(N)
の形式で行う。
- 入力例
3 170
160
165
150
- 出力例
tall
解答
N,T=gets.split.map &:to_i
X=N.times.map{gets.to_i}.reduce(:+)/N
if T>X
puts "tall"
elsif T<X
puts "short"
else
puts "normal"
end
(132bytes)
これをちびちびと短くしていきます。
パターンA ... putsの重複をなくす
if文の返り値をそのまま出力します。
N,T=gets.split.map &:to_i
X=N.times.map{gets.to_i}.reduce(:+)/N
puts (if T>X
"tall"
elsif T<X
"short"
else
"normal"
end)
(124bytes)
パターンB ... 三項演算子を使う
お馴染みの三項演算子を使います。
N,T=gets.split.map &:to_i
X=N.times.map{gets.to_i}.reduce(:+)/N
puts (T>X)?"tall":((T<X)?"short":"normal")
(110bytes)
パターンC ... 宇宙船演算子と配列を使う
宇宙船演算子<=>
は、比較を行った結果として-1, 0, 1
のいずれかを返します。
Rubyの配列に対して負のindexを参照しようとすると、配列の後ろから順番に取り出されるので、
結果が三つしかない場合はこの二つを組み合わせると都合がいいです。
(※…"宇宙船演算子"の名前を"ダイヤモンド演算子"と誤って記載していたので修正しました。@irxground さん、ご指摘ありがとうございます!)
宇宙船演算子と配列を使った解答
N,T=gets.split.map &:to_i
X=N.times.map{gets.to_i}.reduce(:+)/N
puts ["normal","tall","short"][T<=>X]
(105bytes)
Stringの代わりにSymbolを使う。
N,T=gets.split.map &:to_i
X=N.times.map{gets.to_i}.reduce(:+)/N
puts [:normal,:tall,:short][T<=>X]
(102bytes)
配列の宣言方法を変える。
N,T=gets.split.map &:to_i
X=N.times.map{gets.to_i}.reduce(:+)/N
puts %w|normal tall short|[T<=>X]
(101bytes)
Xの取得部分を$<に置き換える。
N,T=gets.split.map &:to_i
X=$<.map(&:to_i).reduce(:+)/N
puts %w|normal tall short|[T<=>X]
(90bytes)
Xの宣言を削除
N,T=gets.split.map &:to_i
puts %w|normal tall short|[T<=>$<.map(&:to_i).reduce(:+)/N]
(86bytes)
132bytes -> 86bytesまで削れました!
終わりに
Rubyでこの手の問題を解くと、1つの問題に対して100通りくらいの書き方がありそうなので、工夫し甲斐があると思います。
解答をちびちび短くしていく作業は結構楽しいので、AtCoder Beginner Contest等で是非Rubyを使ってみてください。