コードゴルフとはプログラムのソースコードをどれだけ短く書けるかを争う競技です。
自分は時々 Anarchy Golf や yukicoder でゴルフしています。
この記事では自分がコードゴルフを行っている時のTipsでも紹介しようと思います。
余分な空白の削除
普通はソースコードの可視性を考えてインデントやスペース等を使用しますが、コードゴルフでは必要ないので削ります。
# bad
puts "hello world"
# good
puts"hello world"
余分な括弧の削除
必要の無い括弧は消しましょう。
# bad
puts("hello")
[1,2,3].map(&:to_s)
# good
puts"hello"
[1,2,3].map &:to_s
変数
基本的に変数は1文字で定義します
# bad
word = 'ruby'
# good
w='ruby'
同じ値を宣言する場合にはまとめることが出来ます
# bad
a=3
b=3
# good
a=b=3
ただし、代入する値が文字列や配列の場合には値に対する変更の影響を受けてしまうので注意が必要です。
多重代入
Rubyでは多重代入が使えます、よくあるパターンは配列の値を各変数に割り当てたい時などに使用します。またsplat演算子 *
を使うと値をまとめて受け取ることが出来ます。
a,b,c=[1,2,3] #=> a=1, b=2, c=3
a,b,*c=[1,2,3,4,5] #=> a=1, b=2, c=[3,4,5]
a,*b,c=[1,2,3,4,5] #=> a=1, b=[2,3,4], c=5
a,*b=1 #=> a=1, b=[]
グローバル変数
rubyでは変数名の先頭に $
があるとグローバル変数になりますが、定義されていないグローバル変数を呼び出しても nil
が返ってくるだけでエラーになりません
$a #=> nil
1文字でnilを取得する
p
メソッドは何も出力しない場合に nil
を返すのでこれを利用して1文字でnilを取得することが出来ます。
p p #=> nil
短い方のメソッド名を使用する
Rubyは 同じ動作をするけどメソッド名が異なる ものがたくさんあります。コードゴルフでは名前が短い方のメソッドを使用しましょう。
# bad
[1,2,3].find_index(2) #=> 1
# good
[1,2,3].index(2) #=> 1
ブロックはdo end
ではなく {...}
を使う
# bad
10.times do|n|puts"#{n} hello"end
# good
10.times{|n|puts"#{n} hello"}
改行コードをなるべく少なくする
改行コードが \n
な環境では気にしなくて良いですが、Windows環境だと改行コードが \r\n
で定義されているので、;
でコードを区切って行数を減らします。
# bad
puts'hello'
puts'world'
# good
puts'hello';puts'world'
ドキュメントを読む
短く出来なくなって詰まったときは、ひとまずドキュメントを見直すことをおすすめします。こんなメソッドあったんだ
とか こんな引数とれたのか
とか ブロックも取れるのか
みたいな新たな気付きがあるときがあったりします。
比較演算子はなるべく1文字で
==
や <=
や >=
といった比較演算子は可能であれば >
や <
に置き換えましょう
# bad
n <= 3
# good
n < 4
これとは別ですがRangeの ..
も ...
を使うことで短くなるパターンがあります。
# bad
(0..n-1).each{|n|p n}
# good
(0...n).each{|n|p n}
論理演算子を条件式に
短絡評価を利用して論理演算子である &&
と ||
を条件式として利用します。
# bad
if true
n=3
end
# good
true&&n=3
# bad
unless false
n=3
end
# good
false||n=3
三項演算子
# bad
if 3>2
333
else
777
end
# good
3>2?333:777
入力
rubyで入力を受け付けるには色々な方法があります。ここでは下のようなテキストデータをどのように取得するかについて考えます
okinawa
ruby
golf
3回 gets
を実行する
3.times do
p gets
end
"okinawa\n"
"ruby\n"
"code golf\n"
gets
を使うと1行情報が取得出来るので、それを3回実行してます。
$<.read
p $<.read
"okinawa\nruby\ncode golf\n"
渡された入力を全て受け取ることが出来ます。
*$<
p *$<
"okinawa\n"
"ruby\n"
"code golf\n"
rubyのsplat演算子は対象のオブジェクトに対して to_a
を実行するので、それを利用しています。結果として $<.readlines
と同じ挙動になります。
コマンドラインオプション
-n
-n
オプションではgetsした値が特殊変数 $_
に代入されます
#!ruby -n
puts $_
okinawa
ruby
code golf
-p
-p
オプションは -n
とほぼ一緒の動作ですが、最後に $_
に格納されている値を出力します。
#!ruby -p
okinawa
ruby
code golf
これは下のコードと同じ動作です。
while gets
print $_
end
-a
-n
か -p
と一緒に利用します。-a
オプションが指定されていると読み込んできた各行に対して split
が自動的に実行され、その結果が $F
に格納されます。
#!ruby -na
p $F
["okinawa"]
["ruby"]
["code", "golf"]
-l
読み込んできた各行に対して chop!
が実行され、printの出力時に改行コードをつけてくれます。
#!ruby -nl
p $_
"okinawa"
"ruby"
"code golf"
-r
require
と同じ動作をします、問題によっては標準ライブラリが持っているメソッドを使用したほうが短くなるときもあるので、その時に使ったりします。
#!ruby -rprime
p 3.prime? #=> true
出力
数値の出力
数値系の出力は p
メソッドが良いです
p 3
その他の出力
他の出力にはputs
と print
と $><<
がありますが、文字数的に puts
と $><<
をよく使います。 $><<
は出力したい要素との間にスペースを入れなくていいので、こちらのほうが短くなるケースが多いです。
s="hello ruby"
puts s #=> hello ruby
$><<s #=> hello ruby
グローバル変数の出力
グローバル変数は間にスペースを入れなくても動作します。また、変数展開する際に {}
を省略することが出来ます。
$a='ruby'
puts$a
$a='ruby'
# bad
puts"hello #{$a}" #=> hello ruby
# good
puts"hello #$a" #=> hello ruby
リテラル
ハッシュ
# bad
h=Hash.new
# good
h={}
文字列
# bad
puts "hello"
# good
puts :hello
シンボルは出力時に文字列に変換されます
文字列
文字リテラル
?
の後に1文字指定するとその文字列が返ってきます。
?a #=> "a"
?3 #=> "3"
?\n #=> "\n"
文字列分割 (String#split)
String#split
ではデフォルトで半角空白、改行などの空白文字で分割を行うので引数で指定する必要はありません。
# bad
"hello world".split(" ") #=> ['hello', 'world']
"hello\nworld".split(?\n) #=> ['hello', 'world']
"hello\tworld".split(?\t) #=> ['hello', 'world']
# good
"hello world".split #=> ['hello', 'world']
"hello\nworld".split #=> ['hello', 'world']
"hello\tworld".split #=> ['hello', 'world']
一桁の数値変換
# bad
p "7".to_i #=> 7
# good
p "7".hex #=> 7
一桁の数値は hex
を使うことで to_i
より短く書くことが出来ます。
evalを使って数値変換
eval
を使用したほうが短くなる場合もあります。(変数のスコープ的に定数で宣言しないと利用できませんが)
n=%w(1 2 3)
a,b,c=n.map &:to_i
p [a,b,c] #=> [1, 2, 3]
eval"A,B,C="+n*?,
p [A,B,C] #=> [1, 2, 3]
数値変換の際に基数を指定する
p "100".to_i #=> 100
p "100".to_i(2) #=> 4
p "100".to_i(8) #=> 64
p "100".to_i(16) #=> 256
to_i
は引数に変換する基数を指定することが出来ます
1文字ずつ分割
# bad
"ruby".split('') #=> ['r', 'u', 'b', 'y']
# good
"ruby".chars #=> ['r', 'u', 'b', 'y']
マッチ
# bad
if 'ruby'=~/r/
puts 'hello!'
end
# good
if 'ruby'[?r]
puts 'Hello!'
end
繰り返し
文字列に対して *(数値)
の操作を行うとその指定した回数分の文字列が繰り返されます
'hello'*0 #=> ''
'hello'*1 #=> 'hello'
'hello'*2 #=> 'hellohello'
数値
リテラル
# bad
n=1000
# good
n=1e3 #=> 1000.0
基数変換
to_s
を使用して文字列に変換する際に引数で変換する基数を指定することが出来ます。
16.to_s #=> "16"
16.to_s(2) #=> "10000"
16.to_s(8) #=> "20"
16.to_s(16) #=> "10"
ビット値取得
数値に対して []
を使用すると指定した場所のビット値が取得できます。
p 1[0] #=> 1
p 2[0] #=> 0
p 3[0] #=> 1
演算の順番を変える
通常の演算では +
より *
が優先されるため、+
のほうの演算を先に行いたい場合は ()
で囲む必要がありますが、2+1
の部分を 3.*
の引数として渡すことで ()
で囲むより1byte短く書くことが出来ます。
# bad
p 3*(2+1) #=> 9
# good
p 3.*2+1 #=> 9
配列
リテラル
# bad
a=Array.new
# good
a=[]
# bad
a=['hoge','piyo','fuga']
# good
a=%w(hoge piyo fuga)
# bad
a=[1,2,3,4,5]
# good
a=*1..5
join
# bad
[1,2,3].join(?+) #=> "1+2+3"
# good
[1,2,3]*?+ #=> "1+2+3"
uniq
# bad
[1,1,2,2,3,3].uniq #=> [1,2,3]
# good
[1,1,2,2,3,3]|[] #=> [1,2,3]
push
# bad
[1,2,3].push(4) #=> [1, 2, 3, 4]
# good
[1,2,3]<<4 #=> [1, 2, 3, 4]
unshift
# bad
[1,2,3].unshift(4) #=> [4, 1, 2, 3]
# good
[1,2,3][0,0]=4 #=> [4, 1, 2, 3]
compact
p [nil,1,2,3,nil].compact #=> [1, 2, 3]
p [nil,1,2,3,nil]-[p] #=> [1, 2, 3]
reverse
2つの要素においては rotate
のほうが短いです。
a=[1,2]
# bad
a.reverse #=> [2, 1]
# good
a.rotate #=> [2, 1]
配列内の合計値を求める
# bad
[1,2,3].inject(:+) #=> 6
# good
eval [1,2,3]*?+ #=> 6
# very good (2.4 から可能)
[1,2,3].sum
ビット演算
# bad
n<-10
# good
n<~9
n=3
# bad
(n+1)*3 #=> 12
# good
-~n*3 #=> 12
n=3
# bad
(n-1)*3 #=> 6
# good
~-n*3 #=> 6
特殊変数
Rubyには $
から始まる様々な特殊変数が存在しています。コードゴルフでは正規表現周りやコマンドラインオプションを有効にしたときに出てくるものがよく使われています。
$_
getsした値が格納されます、オプションで -n
か -p
を指定していると自動的に値が代入されます。
$*
Object::ARGV
の別名ですが、ゴルフ的には最初から用意されている空の配列になります。
# bad
a=[]
# good
$*
$`, $&, $'
$&
は正規表現でマッチした文字列が格納され、 $` にはマッチした文字列の前の文字列部分が格納され、 $'
には マッチした文字列の後ろの文字列が格納されます。
"hello123world" =~ /\d+/
p [$`, $&, $'] #=> ["hello", "123", "world"]
$1, $2, ... $n
正規表現のパターンマッチでn番目の括弧にマッチした文字列が格納されます。
"123 456 789" =~ /(\d+) (\d+) (\d+)/
p [$1, $2, $3] #=> ["123", "456", "789"]
$/
デフォルトで改行文字が入っています。
# bad
?\n
# good
$/
$<
定数 Object::ARGF の別名です。
その他
カテゴリ分けに困ったTipsを書いていきます
Procの呼び出し
Procは call
以外に []
でも呼び出すことが出来ます
func=->word{
puts word
}
# bad
func.call('hello')
# good
func['hello']