コンテンツへスキップ

【Ruby基礎】AtCoder Beginner Contest 009 B – 心配性な富豪、ファミリーレストランに行く。

  • by

■概要

  • tallyメソッドを使って最多要素を出すか、uniqメソッドを使って重複を省いた上で2番目に大きい数字を出す。

■はじめに

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

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

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

■問題

●出典

AtCoder Beginner Contest 009のB問題
https://atcoder.jp/contests/abc009/tasks/abc009_2

●問題文

私は富豪だ。それも大富豪と言っていいぐらいお金を持っている。欲しいと思ったものはまずこの有り余るお金を使って買うことができる。しかし、この底の尽きないように思えるほどのお金でさえ人の心を買うことはできない。いくらお金があろうとも、ひとたび多くの庶民の反発を買ってしまえば、これまでのように生きていくことは難しくなるだろう。

この度私は庶民の気持ちを理解するため、初めてファミリーレストランという場所を訪れた。メニューを広げ、料理の内容とその金額を確かめると、なるほど驚きの安さである。どの料理の金額も取るに足らないようなものだから、とりあえず最も金額が高いものを選ぼうかと考えた。

しかし、考えてみれば、私は何のためにファミリーレストランに来たのであったか。庶民の気持ちを理解しようというのに、金額のことを考えずに最も高いものを選ぼうなどと、まるで意味がないではないか。ファミリーレストランに来たうえ、これ見よがしに最も高い料理を注文したとなったら、私の悪評が広まってしまう可能性だってある。

とはいえ、せっかくだから高いものを選んでその味をみてみたいというのも確かである。そうだ、そういうことなら、この店で 2 番目に高い料理を注文することにしよう。そう思って料理の金額を書き出してみたが、料理の種類が多いために 2 番目に高いものを探すのはなかなか骨が折れる。自分で探すかわりに、プログラムを書いてなんとかできないだろうか?

おっと、プログラムを書き始める前にひとつ言っておくが、最も高い金額の料理が複数あるときには注意してもらいたい。というのは、たとえば 4 種類の料理があり、それぞれの金額が 100 円、200 円、300 円、300 円であったときには、2 番目に高いものというのは 200 円の料理になるということだ。

●入力

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

N
A1
A2
:
AN
  • 1 行目には、料理の種類の個数を表す整数 N (2≦N≦100) が与えられる。
  • 2 行目から N 行では、それぞれの料理の金額が与えられる。N 行のうち i 行目には整数 Ai (1≦Ai ≦1,000) が書かれており、これは i 番目の料理の金額が Ai 円であることを表す。すべての料理の金額が同じであることはない。

●出力

N 個の料理のうち、2 番目に高いものの金額を 1 行に出力せよ。
出力の末尾にも改行をいれること。

■回答

●愚直に書く

問題文が面白い。
回答としては、前回も使ったtallyメソッドでいけそうか?

n = gets.to_i
a = n.times.map do |p|
  p = gets.to_i
end

puts a.tally.sort[-2][0]

通った!
前回はtallyして値の方をソートするためにsort_byを使ったけど、今回はキーの方をソートなのでsortで良くて、前回よりもシンプルなコードになった。(2番目に高いものなので、[-2]で後ろから2番目の要素を取っている)

●メソッド化して書く

メソッドを作る練習のために、あえてそういう書き方をする。
今回はメインメソッド、2番目に金額が高いものを出すメソッド、1行目を取得するメソッド、2行目以降を取得するメソッドの4つにした。

def main
  h = read_menus
  puts judge_second_highest(h)
end

def judge_second_highest(h)
  h.tally.sort[-2][0]
end

def read_times
  gets.to_i
end

def read_menus
  n = read_times
  n.times.map do |p|
    p = gets.to_i
  end
end

main

通った!

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

前回tallyを使ったので反射的に今回も使っていたけど、今回に関しては「並び替えして重複を削除して最後から2番目」という方がシンプルなのでは?

n = gets.to_i
a = n.times.map do |p|
  p = gets.to_i
end

puts a.sort.uniq[-2]

通った!こちらのがシンプル。

●他の方の回答例

上位の皆さんもuniq使っていた。

●出てきたメソッド等

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

■振り返りなど

前回使ったtallyの復習もできたし、uniqの解法も自分で思いつくことができてよかった。