Help us understand the problem. What is going on with this article?

RubyのコードゴルフのTips

More than 1 year has passed since last update.

コードゴルフとはプログラムのソースコードをどれだけ短く書けるかを争う競技です。

自分は時々 Anarchy Golfyukicoder でゴルフしています。

この記事では自分がコードゴルフを行っている時の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

その他の出力

他の出力にはputsprint$><< がありますが、文字数的に 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']

参考ドキュメント

siman
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした