これは
競技プログラミングの入門レベルを通してRubyの基本を学んで行こうぜってものです。出発点が競プロなので記載内容に若干偏りがあります。ソーリー
Rubyを既にある程度学んでる人が「Rubyで競プロしようぜ」ってものではないです。あと、あくまでRubyを学ぶのが趣旨で、Ruby on Railsは対象外です。
みんな忙しいから書いてないんだな、そうだよね
ってことで、まずは基本から、、、
標準入力
# 単一整数
N = gets.to_i
# スペース区切りの複数の整数
A = gets.split.map(&:to_i)
# 縦に並ぶ複数の整数
A = N.times.map { gets.to_i }
# 文字列。chompで改行文字を削除
S = gets.chomp
gets
標準入力から(文字列として)1行読み込む。改行文字が入っていることに注意。
同じ機能を持つ関数として readline がある。こっちはEOF errorを発生させる点だけが異なる。(競プロではgetsで十分)
to_i
文字列を数値に変換する。
他に代表的な変換系メソッドは
to_s
-> 文字列へ変換
to_f
-> 浮動小数点数へ変換
split
文字列クラスのメソッド。指定した文字列で配列に分割する。文字列を指定しなかった場合は空白文字で分割する。
空白文字とはスペースだけでなく、改行文字なども含まれる
map(&:to_i)
とは
まずはブロックを理解する
# 基本の形
numbers = [1, 2, 3, 4]
sum = 0
numbers.each do |n|
sum += n
end
# 1行でも書ける
numbers.each do |n| sum += n end
# {}でもブロックを作れる
numbers = [1, 2, 3, 4]
sum = 0
numbers.each { |n|
sum += n
}
# これも1行で書いてok
numbers.each { |n| sum += n }
で、mapとは
他の言語で見られるmapと同じように、配列の各要素に対して、ブロックに渡した処理を行なって新しい配列にして返す。
# 例
['ruby', 'java', 'golang'].map { |s| s.upcase }
#=> ['RUBY', 'JAVA', GOLANG]
最後に&:to_i
を理解する
直前のmap
の説明で使ったプログラムは以下に省略できる
['ruby', 'java', 'golang'].map(&:upcase)
この記法ができる条件は
- ブロックの引数が1つだけ(
|s|
だけ) - ブロック内で呼び出すメソッドに引数がない(
upcase
に引数なし) - ブロック内で、ブロック引数に対してメソッドを呼び出す以外の処理がない(
s.upcase
の処理のみ)
標準出力
これはさらっと
# 引数を文字列に変換して出力。改行あり。基本これでいい
puts obj
# putsの改行なしver
print obj
# デバッグ用。引数のオブジェクトを出力する
p obj
# デバッグ用。よりフォーマットされたものを出力する
pp obj
実践編
やっと本題。定番のAtCoder Beginners Selectionから
1. Product
問題
シカのAtCoDeerくんは二つの正整数 a,b を見つけました。 a と b の積が偶数か奇数か判定してください。
入力
a b
出力
積が奇数なら Odd と、 偶数なら Even と出力せよ。
解法
a, b = gets.split.map(&:to_i)
product = a * b
puts product.odd? ? 'Odd' : 'Even' # 三項演算子を使用
a, b =
って
多重代入可能。
# 例
foo, bar = [1, 2] # foo = 1; bar = 2
foo, bar = 1, 2 # foo = 1; bar = 2
foo, bar = 1 # foo = 1; bar = nil
foo, bar, baz = 1, 2 # foo = 1; bar = 2; baz = nil
foo, bar = 1, 2, 3 # foo = 1; bar = 2
foo = 1, 2, 3 # foo = [1, 2, 3]
*foo = 1, 2, 3 # foo = [1, 2, 3]
foo,*bar = 1, 2, 3 # foo = 1; bar = [2, 3]
odd?
奇数を判定して真偽値を返してくれる。当然even?もある。
慣例として、Rubyでは真偽値を返す変数や関数には?
をつける。
この時、prefixにis_
can_
などを付けない。
また、自身を変更するような破壊的メソッドには!
をつける
e.g.) obj.update!
ちなみに、Rubyは公式にはstyle guideはなさそう
式展開(string interpolation)
name = 'ヒロミ'
puts "私の名前は#{name}です。"
#=>私の名前はヒロミです。
基本的に文字列は""
でも ''
でもok。
ただし、式展開する場合はダブルクォーテーションでないとダメ。
2. Placing Marbles
問題
すぬけ君は 1,2,3 の番号がついた3つのマスからなるマス目を持っています。各マスには0か1が書かれており、マスiにはs(i)が書かれています。
すぬけ君は 1 が書かれたマスにビー玉を置きます。 ビー玉が置かれるマスがいくつあるか求めてください。
入力
s(1)s(2)s(3)
出力
答えを出力せよ。
解法
s_arr = gets.chars.map(&:to_i)
count = 0
for s in s_arr
count += 1 if s == 1 # 後置if
end
puts count
Rubyのfor文(繰り返し)パターン
# 範囲オブジェクトを繰り返す
# https://docs.ruby-lang.org/ja/latest/class/Range.html
for i in 0...5 do # doは省略可能
puts i
end
# 配列を繰り返す
for e in [1, 2, 3]
puts e
end
# ブロックで繰り返す
[1, 2, 3].each do |e|
puts e
end
3. Shift only
問題
黒板にN個の正の整数A(1),...,A(N)が書かれています。
すぬけ君は黒板に書かれている整数がすべて偶数であるとき、次の操作を行うことができます。
- 黒板に書かれている整数すべてを,2 で割ったものに置き換える
すぬけ君は最大で何回操作を行うことができるかを求めてください
入力
N
A(1) A(2)...A(N)
出力
すぬけ君は最大で何回操作を行うことができるかを出力せよ
解法
_ = gets.to_i # 未使用変数をdiscardする
arr = gets.split.map(&:to_i)
count = 0
while arr.all?(&:even?) do # このdoも省略可能
arr = arr.map { |a| a / 2 }
count += 1 # インクリメントするにはcount.succ/count.nextとかもある
end
puts count
all?
ブロック内の条件を全て満たしているかチェックしてくれる便利なメソッド。
他にも似たのが色々あるみたい
もっと網羅したのはこっち
4. Coins
問題
あなたは、500円玉を A枚、100円玉をB枚、50円玉をC枚持っています。これらの硬貨の中から何枚かを選び、合計金額をちょうど X 円にする方法は何通りありますか。
同じ種類の硬貨どうしは区別できません。2通りの硬貨の選び方は、ある種類の硬貨についてその硬貨を選ぶ枚数が異なるとき区別されます。
入力
A
B
C
X
出力
硬貨を選ぶ方法の個数を出力せよ。
解法
a = gets.to_i
b = gets.to_i
c = gets.to_i
x = gets.to_i
count = 0
for i in 0..a
a500 = 500 * i
for j in 0..b
b100 = 100 * j
for k in 0..c
c50 = 50 * k
count += 1 if a500 + b100 + c50 == x
end
end
end
puts count
Rubyの知識で特に目新しいものなし
5. Some Sums
問題
1以上N以下の整数のうち、10進法での各桁の和がA以上B以下であるものの総和を求めてください。
入力
N A B
出力
1以上N以下の整数のうち、10進法での各桁の和がA以上B以下であるものの総和を出力せよ。
解法
n, a, b = gets.split.map(&:to_i)
sum = 0
(1..n).each do |integer|
digits_sum = integer.digits.sum
sum += integer if a <= digits_sum && b >= digits_sum
end
puts sum
(だんだん新しく得られるrubyの情報が少なくなってきた。。。 )
復習内容
-
n, a, b = gets.split.map(&:to_i)
配列の多重代入 -
(1..n).each do |integer|
範囲オブジェクトは繰り返し処理できる -
sum += integer if
後置if
integer.digits.sum
数値を配列にして返してくれる digits
配列の要素の合計を返す sum
この二つのメソッドで
10進法での各桁の和
を解決
6. Card Game for Two
問題
N枚のカードがあります。i枚目のカードには, a(i)という数が書かれています。
AliceとBobはこれらのカードを使ってゲームを行います。ゲームではAliceとBobが交互に 1枚ずつカードを取っていきます。Alice が先にカードを取ります。
2人がすべてのカードを取ったときゲームは終了し、取ったカードの数の合計がその人の得点になります。2人とも自分の得点を最大化するように最適な戦略を取った時,、AliceはBobより何点多く取るか求めてください
入力
N
a(1) a(2) a(3) ... a(N)
出力
両者が最適な戦略を取った時, AliceはBobより何点多く取るかを出力してください
解法
n = gets.to_i
cards = gets.split.map(&:to_i)
sorted_cards = cards.sort
alice = 0
bob = 0
for i in 0...n
if i.even? # 0は偶数だよ
alice += sorted_cards.pop
else
bob += sorted_cards.pop
end
end
puts alice - bob
sort
やっと来たぞソート
arr = [9, 7, 10, 11, 8]
p arr.sort #=> [7, 8, 9, 10, 11]
# sort! で自身を破壊的にソート
arr.sort!
p arr #=> [7, 8, 9, 10, 11]
# sort_byを使ってブロック内で比較対象を指定してソート
arr2 = ["9", "7", "10", "11", "8"]
p arr2.sort_by{ |x| x.to_i } #=> ["7", "8", "9", "10", "11"]
pop
配列の末尾を取り出して返す。(自身も変更)
配列操作の基本は押さえておきたいところ。
(丸投げ!!)
7. Kagami Mochi
問題
X段重ねの鏡餅(X≥1)とは、X枚の円形の餅を縦に積み重ねたものであって、どの餅もその真下の餅より直径が小さい(一番下の餅を除く)もののことです。例えば、直径10、8、6センチメートルの餅をこの順に下から積み重ねると3段重ねの鏡餅になり、餅を一枚だけ置くと1段重ねの鏡餅になります。
ダックスフンドのルンルンは N 枚の円形の餅を持っていて、そのうち i 枚目の餅の直径は d(i)センチメートルです。これらの餅のうち一部または全部を使って鏡餅を作るとき、最大で何段重ねの鏡餅を作ることができるでしょうか。
入力
N
d(1)
:
d(N)
出力
作ることのできる鏡餅の最大の段数を出力せよ。
解法
n = gets.to_i
arr = n.times.map { gets.to_i }
sorted = arr.sort
x = []
x.push(sorted.pop)
while !sorted.empty?
d = sorted.pop
x.push(d) if d < x.last
end
puts x.count
empty?
レシーバが「空」であればtrueを返すメソッド。
つまり文字列なら空文字、配列なら空配列の場合にtrueを返す。
nilチェックにはnil?を使う必要があり、nilオブジェクトでempty?を呼ぶとNoMethodErrorが発生するので注意
最後に
実はあと3問くらいありますが、これ以上Rubyの目新しい知識は出てきません。
なので、ここら辺で終わります。てか自分の担当日まであと1時間しかないんで、、、
最後の方だんだん説明が雑になってなんかすいません
Rubyはサクッと書けるからいいね!
(W杯が朝の4時からあるから準備しないと!!ほな!!)