コンテンツへスキップ

【Ruby基礎】AtCoder Beginner Contest 030 B – 時計盤

  • by

■はじめに

Rubyの基礎的な問題をたくさん解くことで基本的な考え方やメソッドの使い方を定着させたい。
基本的にはAtCoderというプログラミングコンテスト(競技プログラミング)の過去問を使う。(AtCoderは難易度が分かれており、難易度の低いA問題かB問題を解いていく)

(5/23時点の方針)
メソッドの切り分け方や値の受け渡しを練習するために、コード長の短さについては気にせずに書くことにする。

(2022/10/17時点の方針)
しばらくはB問題を小さい番号の方からやっていく。たまにA問題もやるかも。

■問題

●出典

AtCoder Beginner Contest 030のB問題
https://atcoder.jp/contests/abc030/tasks/abc030_b

●問題文

n 時 m 分のアナログ時計があります。短針と長針のなす角度のうち小さい方を度数法で求めてください。

●入力

入力は以下の形式で標準入力から与えられる。

n m
  • 1 行目には、アナログ時計の時刻を表す整数 n,m(0≦n≦23,0≦m≦59) が空白区切りで書かれている。

●出力

短針と長針のなす角度を 1 行目に出力せよ。絶対誤差または相対誤差が 10^−4 以下であれば許容される。
末尾の改行を忘れないこと。

■回答

●愚直に書く

まず短針は24時間表記になってるから12時間表記に直す。
その上で、短針と長針をそれぞれ角度に変換して、差の絶対値を取る感じか…?

n, m = gets.split.map(&:to_i)
n = n % 12

hour = n * 30
minute = m * 6

puts (hour - minute).abs

これだと駄目。
プリントデバッグしてみたら、短針が分数に合わせて少しずつ動く分を考慮できていなかった。そりゃそうだ。

分数分を短針も動かすという処理さえできればうまくいくかな?

1時間で短針は5度、つまり1分あたり5/60 = 1/12度が動くということか。

n, m = gets.split.map(&:to_i)
n = n % 12

minute = m * 6
hour = n * 30.0 + minute / 12.0

puts (hour - minute).abs

むむむ、これだと一部のケースでWAになる。なぜだ…。

あ〜そうか、一部の場合で大きい方の角度を出してしまっているのか。
minutehourは正しいはずなので、最後に条件分岐すればいけるかな?

n, m = gets.split.map(&:to_i)
n = n % 12

minute = m * 6
hour = n * 30.0 + minute / 12.0

angle = (hour - minute).abs
puts angle > 180 ? 360 - angle : angle

通った!手こずったけど、一歩ずつ正解に進んでいった感があり嬉しい。

●メソッド化して書く

メソッドを作る練習のために、あえてそういう書き方をする。

色々頭が混乱したけど、以下のメソッドを作成。

  • メインメソッド
  • 標準入力1つ目を長針の角度に変換
  • 標準入力2つ目を短針の角度に変換
  • 長針と短針の角度を計算
  • 標準入力を取得
def main
  n, m = read_nums
  n = n % 12

  minute = convert_minute_to_angle(m)
  hour = convert_hour_to_angle(n, minute)

  angle = calc_angle(hour, minute)
  puts angle > 180 ? 360 - angle : angle
end

def convert_minute_to_angle(m)
  m * 6
end

def convert_hour_to_angle(n, minute)
  n * 30.0 + minute / 12.0
end

def calc_angle(hour, minute)
  (hour - minute).abs
end

def read_nums
  gets.split.map(&:to_i)
end

main

通った!
もう少し見通しを良くできるかもしれないけど、ここで時間切れ。

●リファクタリング/別アプローチ

思いつかないので割愛。

●他の方の回答例

上位回答は、変数を使わなかったりしてかなりコード長を圧縮しているけど考え方としては大体同じような感じ、かな?

一点、minメソッドの活用が参考になったのでメモ。
最後のangle > 180 ? 360 - angle : angleは、角度が180度より大きかったら小さい方の角度を計算する、という流れだけど、[angle, 360 - angle].minとすれば、2つの角度の小さい方を出力することになり、スマート。

●出てきたメソッド等

公式リファレンスを見る訓練。

■振り返りなど

minメソッドのこういう使い方をサラッとできるようになりたいけど今回は気づけなかったな〜。