コンテンツへスキップ

【Ruby基礎】AtCoder Beginner Contest 026 B – N重丸

■はじめに

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

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

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

■問題

●出典

AtCoder Beginner Contest 026のB問題
https://atcoder.jp/contests/abc026/tasks/abc026_b

●問題文

高橋君は、丸が大好きです。今日も、原点を中心とした大きさの違う円を N 個書きました。

その円の集合に対し、外側から赤白交互に色を塗ったとき、赤く塗られる部分の面積を出力しなさい。

●入力

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

N
R1
R2
:
RN
  • 1 行目には、円の個数を表す整数 N(1≦N≦1000) が与えられる。
  • 2 行目から N 行は、円の半径の情報を表す N 個の整数が、1 行ずつ与えられる。そのうち i 番目は、 i 番目の円の半径を表す整数 Ri (1 ≦ Ri ≦ 1000) である。
  • i ≠ j の時、 Ri ≠ Rj を満たす。

●出力

赤く塗られる部分の面積を 1 行で出力せよ。 答えは、相対誤差または絶対誤差が 10^−6 以下であれば許容される。

出力の末尾には改行を入れること。

■回答

●愚直に書く

  • 数字を大きい順に並べる
  • それぞれの数字を半径とする円の面積を出して配列に入れる
  • 一番外側の縁がindex(0)番目の円で、次がindex(1)。なので、偶数番目の円の面積をプラス、奇数番目の円の面積をマイナスしていけば答えになる、はず
n = gets.to_i
r = n.times.map{|r| r = gets.to_i}.sort.reverse

area_of_circle = r.map{|r| r**2 * Math::PI}

p area_of_circle.map{|a|
  if area_of_circle.index(a).odd?
    -a
  else
    a
  end
  }.sum

通った!

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

ひとまず後半部分の整理と、変数名も少し変更。

n = gets.to_i
r = n.times.map{|r| r = gets.to_i}.sort.reverse
array_circle = r.map{|r| r**2 * Math::PI}

p array_circle.map{|a| array_circle.index(a).even? ? a : -a}.sum

あとは、円周率をかけるのは一番最後にした方が計算が早くなるかな??

n = gets.to_i
r = n.times.map{|r| r = gets.to_i}.sort.reverse
array_square = r.map{|r| r**2}

p array_square.map{|a| array_square.index(a).even? ? a : -a}.sum * Math::PI

通った!実行時間もほんの少し早くなった。

●メソッド化して書く

メソッドを作る練習のために、あえてそういう書き方をする。
今回はややこしい…。

  • メインメソッド
  • 全ての円の面積が入った配列を求めるメソッド
  • 二重丸の面積を求めるメソッド
  • 1行目の標準入力を取るメソッド
  • 2行目以降=全ての半径が入った配列を求めるメソッド
    の5つを作った。
def main
  n = read_num
  r = read_radii(n)
  array_circle = calculate_array_circle(r)
  double_circle = calculate_double_circle(array_circle)
  puts double_circle
end

def calculate_array_circle(r)
  r.map{|r| r**2 * Math::PI}
end

def calculate_double_circle(array)
  array.map{|a| array.index(a).even? ? a : -a}.sum
end

def read_num
  gets.to_i
end

def read_radii(i)
  i.times.map{|r| r = gets.to_i}.sort.reverse
end

main

通った!!

●他の方の回答例

B問題でメソッドチェーンがズラッと1行に続くような書き方は読み解きが難しい。。。
上から順番に見ていって、ようやくなるほどと思う回答があったので自分の回答に当てはめてみる。

n = gets.to_i
r = n.times.map{|r| r = gets.to_i}.sort.reverse
ans = 0
n.times do |i|
    ans += r[i] ** 2 * (i.even? ? 1 : -1)
end
puts ans * Math::PI

ゼロを入れた変数ansを作り、足し上げていく。また、偶数の場合はプラス、奇数の場合はマイナスというのもi.even? ? 1 : -1で処理する。
元々の自分の回答よりこちらのがスマートな感じがする。

●出てきたメソッド等

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

■振り返りなど

  • 変数にゼロを入れて足し上げていく、という方法はとてもよく見るけどまだ慣れていないので、もっと書いていきたい。
  • even?odd?、どっちがどっちかよく忘れる。