【Ruby基礎】AtCoder Beginner Contest 010 B - 花占い

■はじめに

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

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

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

■問題

●出典

AtCoder Beginner Contest 010のB問題 https://atcoder.jp/contests/abc010/tasks/abc010_2

●問題文

高橋君の秘書のなぎさちゃんは、高橋君が大好きです。つまり、高橋君もなぎさちゃんの事が大好きであるに違いありません。 そのことを確認するために、庭に咲いている花で、花占いをすることにしました。

「好き」、「嫌い」、「好き」、「嫌い」、「好き」、「嫌い」……。

おかしいです。高橋君はなぎさちゃんの事が好きであるはずなのに、花占いの結果は「嫌い」でした。 これは、花が悪いに違いありません。

なぎさちゃんは、使用人達に、花占いの結果が「嫌い」にならないように、花びらを毟るよう命じました。

なぎさちゃんの花占いは、2つのパターンがあります。 一つは、「好き」「嫌い」を交互に言いながら、花びらを 1 枚ずつ毟っていくパターンです。 もう一つは、「好き」「嫌い」「大好き」の 3 つを繰り返しながら、花びらを1枚ずつ毟っていくパターンです。

どちらのパターンにおいても、最後に言った言葉が、花占いの結果となります。

なぎさちゃんの使用人であるあなたは、なぎさちゃんがどちらのパターンで花占いをしたときも、「嫌い」にならないように、 花びらを事前に毟ってあげる必要があります。

庭に咲いている花の数と、その花びらの枚数が与えられるので、花びらを毟る必要のある枚数を出力してください。

●入力

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

n
a1 a2 ... an
  • 1 行目には、庭に咲いている花の数を表す整数 n(1≦n≦10) が与えられる。
  • 2 行目では、それぞれの花の花びらの枚数に関する情報が、スペース区切りで与えられる。 i 番目の花の花びらの枚数は、 i 番目に与えられる整数 ai (1≦ai ≦9)によって与えられる。

●出力

毟る必要のある花びらの枚数を 1 行で出力せよ。出力の末尾には改行をいれること。

■回答

●愚直に書く

  • 1→そのまま
  • 2→1枚ちぎる
  • 3→そのまま
  • 4→1枚ちぎる
  • 5→2枚ちぎる
  • 6→3枚ちぎる
  • 7→そのまま(1と同じ)
  • 8→1枚ちぎる(2と同じ)

という感じでループするので、6で割ったあまり(% 6)をしてその結果に応じてちぎる枚数が変わるという感じかな。

n = gets.to_i
a = gets.split.map(&:to_i)

x = a.map{|x| x % 6}

p x.map{|y|
  case y
    when 0
      y = 3
    when 1
      y = 0
    when 2
      y = 1
    when 3
      y = 0
    when 4
      y = 1
    when 5
      y = 2
    end
  }.sum

かなりゴチャッとしてるけど通った! これをもうちょっと整理したい。

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

上の回答はmapを2回別で回しているので、1回にできないだろうか。

n = gets.to_i
a = gets.split.map(&:to_i)

p x = a.map{|x|
  y = x % 6
  case y
  when 0
    y = 3
  when 1
    y = 0
  when 2
    y = 1
  when 3
    y = 0
  when 4
    y = 1
  when 5
    y = 2
  end
}.sum

通った! ひとまずmapの数を減らせた。 もっとシンプルにしたいが…。

1の時と3の時、そして2の時と4の時は変数に入れる値が同じなので集約できるのかな。 変数の名前もちょっとだけ調整。

n = gets.to_i
a = gets.split.map(&:to_i)

p petals = a.map{|p|
  x = p % 6
  case x
  when 0
    x = 3
  when 1, 3
    x = 0
  when 2, 4
    x = 1
  when 5
    x = 2
  end
}.sum

通った! 初め、何も調べずwhen 1 || 3と書いたらうまくいかなかった。複数の値を指定する場合にはカンマを使う。

●メソッド化して書く

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

上で作った回答をメソッド化してみる。

def main
  n = read_num
  f = read_petals
  puts adjust_petals(f)
end

def adjust_petals(petals)
  petals.map{|p|
  x = p % 6
  case x
    when 0
      x = 3
    when 1, 3
      x = 0
    when 2, 4
      x = 1
    when 5
      x = 2
    end
  }.sum
end

def read_num
  gets
end

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

main

通った!read_numメソッドは作らなくてもmainメソッドで直接getsだけ書いておけば良い気もする。

●他の方の回答例

他の方の回答を見ても全然わからない、涙。 injectがとにかくたくさん出てくるなぁ。 順番に見ていってようやくピンときた回答があったので自分の回答に組み込んでみる。

n = gets.to_i
a = gets.split.map(&:to_i)
s = [3, 0, 1, 0, 1, 2]

puts a.map{ |i| s[i % 6] }.inject(:+)

aは標準入力から取得した花ごとの配列。 sは、自分が作ったcase式に該当する、6で割った時の余りに応じてちぎる花びらの数を並べた配列。

mapでaを回して、「6で割った時の余りの数」番目のs配列の数字を割り出して、それをinjectでたたみ込み演算することで足し上げると、回答になる。 かろうじてわかった気がする…!

●出てきたメソッド等

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

■振り返りなど

今日は、難しかったけどちょうど背伸びして理解が進んだので、難易度としてはこれくらいをちゃんとやっていくと良さそう。ただし時間がかかる。