6
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

競プロで学ぶRuby

Last updated at Posted at 2022-12-13

これは

競技プログラミングの入門レベルを通してRubyの基本を学んで行こうぜってものです。出発点が競プロなので記載内容に若干偏りがあります。ソーリー :pray:

:warning: Rubyを既にある程度学んでる人が「Rubyで競プロしようぜ」ってものではないです。あと、あくまでRubyを学ぶのが趣旨で、Ruby on Railsは対象外です。

:point_up_2: みんな忙しいから書いてないんだな、そうだよね:exclamation::question:

ってことで、まずは基本から、、、

標準入力

# 単一整数
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

文字列クラスのメソッド。指定した文字列で配列に分割する。文字列を指定しなかった場合は空白文字で分割する。
:warning: 空白文字とはスペースだけでなく、改行文字なども含まれる

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. ブロックの引数が1つだけ(|s|だけ)
  2. ブロック内で呼び出すメソッドに引数がない(upcaseに引数なし)
  3. ブロック内で、ブロック引数に対してメソッドを呼び出す以外の処理がない(s.upcaseの処理のみ)

標準出力

これはさらっと

# 引数を文字列に変換して出力。改行あり。基本これでいい
puts obj

# putsの改行なしver
print obj

# デバッグ用。引数のオブジェクトを出力する
p obj

# デバッグ用。よりフォーマットされたものを出力する
pp obj

参考 【ruby】p pp puts print 違い。

実践編

やっと本題。定番のAtCoder Beginners Selectionから :smiley_cat:

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はなさそう :eyes: :thinking:

式展開(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?

ブロック内の条件を全て満たしているかチェックしてくれる便利なメソッド。
他にも似たのが色々あるみたい :eyes:

もっと網羅したのはこっち :point_left:

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の知識で特に目新しいものなし :exclamation: :exclamation:

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の情報が少なくなってきた。。。 :sweat_smile: )

復習内容

  • n, a, b = gets.split.map(&:to_i) 配列の多重代入
  • (1..n).each do |integer| 範囲オブジェクトは繰り返し処理できる
  • sum += integer if 後置if

integer.digits.sum

数値を配列にして返してくれる digits
配列の要素の合計を返す sum
この二つのメソッドで

10進法での各桁の和

を解決 :muscle:

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

やっと来たぞソート :exclamation:

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が発生するので注意 :warning:

最後に

実はあと3問くらいありますが、これ以上Rubyの目新しい知識は出てきません。
なので、ここら辺で終わります。てか自分の担当日まであと1時間しかないんで、、、 :sweat_smile:
最後の方だんだん説明が雑になってなんかすいません
Rubyはサクッと書けるからいいね!

(W杯が朝の4時からあるから準備しないと!!ほな!!)

6
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?