【AtCoder:1回目】AtCoder Beginner Contest 126の振り返り(Ruby)

【結論】

Rubyで回答しています。

・ABCしか解けなかったので、振り返りはその3問のみです。

・解説ではなく、自身の振り返りです(クソコードしか書いてない・・・)

【目次】

【本題】

AtCoder Beginner Contest 126の振り返り

初めてAtCoderのコンテストに挑戦したので、その振り返しをしたいと思います。

今回挑戦したのは、5/19(日)に開催された「AtCoder Beginner Contest 126」です。

AtCoder Beginner Contest 126 - AtCoder

結果

6問中、3問しか解けませんでした・・・

それに、デバック用に記述していたputsを消し忘れて「不正解」になる等、凡ミスも多発しました・・・

それでも、初めてにしては上出来だと前向きに捉えています!

A - Changing a Character

では、1問目から振り返ります。

問題文 A, B, C からなる長さ N の文字列 S と、1 以上 N 以下の整数 K が与えられます。 文字列 S の K 文字目を小文字に書き換え、新しくでき S を出力してください。

こちらに対する回答が以下の通りです。

N,K = gets.chomp.split(" ").map(&:to_i);
S = gets.chomp
 
result = []
 
S.chars.each_with_index do |str, index|
  if index == K - 1
    result << str.downcase
  else
    result << str
  end
end
 
puts "#{result.join}"

IF文で該当の文字列を特定し、小文字に置き換えるという処理です。

でも、他の方々の回答を見ていると、もっと良いやり方がありました。

n,k=gets.split.map &:to_i
s=gets
s[k-1]=s[k-1].downcase
puts s

文字列って、配列みたいな方法で文字を指定して取得出来るんですね💦

これで、いちいちIF文で、対象の文字列を特定しなくても、小文字に変換できるので、非常にスマートになりました!

B - YYMM or MMYY

問題文 長さ 4 の数字列 S が与えられます。あなたは、この数字列が以下のフォーマットのどちらであるか気になっています。

YYMM フォーマット: 西暦年の下 2 桁と、月を 2 桁で表したもの (例えば 1 月なら 01) をこの順に並べたもの MMYY フォーマット: 月を 2 桁で表したもの (例えば 1 月なら 01) と、西暦年の下 2 桁をこの順に並べたもの 与えられた数字列のフォーマットとして考えられるものが YYMM フォーマットのみである場合 YYMM を、 MMYY フォーマットのみである場合 MMYY を、 YYMM フォーマット と MMYY フォーマットのどちらの可能性もある場合 AMBIGUOUS を、 どちらの可能性もない場合 NA を出力してください。

私の回答はこちらです。

S = gets.to_s

int = S.scan(/.{1,#{2}}/)
 
if int[0].to_i.between?(1, 12) & int[1].to_i.between?(1, 12)
  puts 'AMBIGUOUS'
elsif !int[0].to_i.between?(1, 12) & !int[1].to_i.between?(1, 12)
  puts 'NA'
elsif !int[0].to_i.between?(1, 12) & int[1].to_i.between?(1, 12)
  puts 'YYMM'
else
  puts 'MMYY'
end

与えられるのは数字列という事でに関しては何も考慮せず、数字列の前方・後方の2桁がにあてはまるか?、つまり01〜12の間にあてはまるのか?だけ見れば良いと考えました。

まず、両方がMMか?否か?を判定した後に、片方づつMMか?判定する様にしています。

他の方の回答を見ると、case文で書かれていたり、正規表現を使ったり、三項演算子を用いてワンライナーで書いている人も居たりしました💦

中でも面白かったのは、下記の様にして、数字列の前方・後方の2桁を分割されているコードでした。

n = gets.to_i
a = n/100
b = n%100

例えば、入力値が「1990」の場合を想定します。

100(整数:integer型)で割ると、小数部分は切り捨てられるので、前方の「19」が得られます。

次に、100での剰余を求めると、後方の「90」が得られます。

これにより前方・後方の2桁を得る事が出来ます。

それを元に、初めのコードを組み替えると、下記の様になります。

S = gets.to_s

a = S/100
b = S%100
 
if a.between?(1, 12) & b.between?(1, 12)
  puts 'AMBIGUOUS'
elsif !a.between?(1, 12) & !b.between?(1, 12)
  puts 'NA'
elsif !a.between?(1, 12) & b.between?(1, 12)
  puts 'YYMM'
else
  puts 'MMYY'
end

先ほどよりスマートになりました。条件式部分もかなり改善の余地がありますが、長くなるので飛ばします。

C - Dice and Coin

問題文 すぬけ君は 1 〜 N の整数が等確率で出る N 面サイコロと表と裏が等確率で出るコインを持っています。すぬけ君は、このサイコロとコインを使って今から次のようなゲームをします。

まず、サイコロを 1 回振り、出た目を現在の得点とする。 得点が 1 以上 K − 1 以下である限り、すぬけ君はコインを振り続ける。表が出たら得点は 2 倍になり、裏が出たら得点は 0 になる。 得点が 0 になった、もしくは K 以上になった時点でゲームが終了する。このとき、得点が K 以上である場合すぬけ君の勝ち、 0 である場合すぬけ君の負けである。 N と K が与えられるので、このゲームですぬけ君が勝つ確率を求めてください。

私の回答がこちらです。

N,K = gets.chomp.split(" ").map(&:to_i);
 
win = []
 
N.times do |n|
  count = 0
  score = n + 1
  while score <= K - 1  do
    score *= 2
    count += 1
  end
  if count == 0
    win << N
  else
    win <<  2**count * N
  end
end
 
win_win = 0.to_f
 
win.each do |num|
  win_win += ( 1 / num.to_f )
end
 
puts win_win

変数名はテキトウです・・・

まず、サイコロの面の数だけ繰り返し処理をする様にtimesを記述します。

次に、勝利に必要なコイントスの回数を計算します。

最後は、コイントスの回数だけ2を累乗して、それにNを掛けます。

こうする事で、そのサイコロの目における勝利確率の分母が出せます(なお、コイントスする前に勝利が確定している場合は、サイコロの目だけを分母とします)

そして、それらの分母で1を割って、計算結果を全て合計すれば、確率が出せます(超ゴリ押し・・・)

他の方の回答で面白かったのは、reduce・Math・log2などを駆使して、解かれていた回答でした(どれも使った事が無い・・・)

https://ref.xaio.jp/ruby/classes/enumerable/reduce

module Math (Ruby 3.2 リファレンスマニュアル)

Math.#log2 (Ruby 3.2 リファレンスマニュアル)

改善点

  • 標準入力を取得する部分など、テンプレート化できる箇所は、テンプレを利用する

  • コードテストしてから提出する(存在を知らなかった)

  • putsとかデバック用に書いた記述の消し忘れ注意

  • リファレンスを読もう!!(知らないメソッドが一杯!!)