コンテンツへスキップ

【Ruby基礎】AtCoder Beginner Contest 054 A – One Card Poker

  • by

INDEX

■はじめに

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

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

■問題

●出典

AtCoder Beginner Contest 054のA問題
https://atcoder.jp/contests/abc054/tasks/abc054_a

●問題文

AliceとBobは、2人で1枚ポーカーを行います。
1枚ポーカーは、トランプを用いて行う2人ゲームです。

今回使用するトランプでは、各カードに 1 から 13 までの数が書かれています。
カードの強さは、カードに書かれている数で決まり,強さの基準は以下の通りです。
弱 2 < 3 < 4 < 5 < 6 < 7 < 8 < 9 < 10 < 11 < 12 < 13 < 1 強

1枚ポーカーは以下の手順で行います。

  1. 各プレイヤーは、トランプからカードを1枚選んで、自分の手札とします。
  2. 両プレイヤーは、手札を見せ合います。強いカードを持っているプレイヤーが勝ちです。
    なお、両プレイヤーの持っているカードの強さが同じ場合は引き分けです。

2人の対戦を眺めていたあなたは、AliceとBobの手札を知ることができます。
Aliceが持っているカードに書かれている数は A 、Bobが持っているカードカードに書かれている数は B です。
2人の代わりに、勝敗を判定するプログラムを作ってください。

●制約

  • 1≦A≦13
  • 1≦B≦13
  • A,B は整数である。

●入力

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

A B

●出力

Aliceが勝つならAliceを、Bobが勝つならBobを、引き分けならDrawを出力せよ。

■回答

●愚直に書く

1の場合には1を14に変えれば単純に大小比較できるかな…?

a, b = gets.split.map(&:to_i)

if a == 1
  a = 14
end

if b == 1
  b = 14
end

if a > b
  puts "Alice"
elsif b > a
  puts "Bob"
else
  puts "Draw"
end

通った!しかし冗長な感じがすごい…。
14にする部分は1行ずつにできるかな。

a, b = gets.split.map(&:to_i)

a = 14 if a == 1
b = 14 if b == 1

if a > b
  puts "Alice"
elsif b > a
  puts "Bob"
else
  puts "Draw"
end

通った!

●メソッド化して書く

メソッドを作る練習のために、あえてそういう書き方をする。
今回はメインメソッド、ポーカーの判定メソッド、標準入力を取ってくるメソッドの3つ。
エースの場合に14にするメソッドも分けようと思ったけどちょっと手こずってしまったので割愛…。

def main
  a, b = read_nums
  a = 14 if a == 1
  b = 14 if b == 1
  puts poker(a, b)
end

def poker(a, b)
  if a > b
    "Alice"
  elsif b > a
    "Bob"
  else
    "Draw"
  end
end

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

main

通った!

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

ポーカー判定の部分をもう少し短くできそうだけど諦めた涙。

●他の方の回答例

ポイントは2つあった。

  • 標準入力を取る時に1→14の処理もしてしまう
  • ポーカー判定を宇宙船演算子で行う

1つ目。
考え方としては、全ての数字を「12を足して14で割った余り」に変換すると、比較がしやすくなる。(1→13、2→0、3→1、、、というように変換される)
この処理をどこでするかというと標準入力を取ってくる際に行なってしまうのが一番スマートそう。
もはや定型文として書いているgets.split.map(&:to_i)を書き換えて、
a, b = gets.split.map{|n| (n.to_i + 12) % 14}とすれば良い。

なるほど〜!a = 14 if a == 1みたいな個別処理をするんじゃなくて全体に処理をするというのがスマートな感じがする。

2つ目。
前回も出てきた宇宙船演算子!!なぜ気が付かなかったのか。
数字の比較で「大・小・等しい」で条件分岐されるような場合には宇宙船演算子を連想できるようにしたい。
今回の書き方としては以下のようになる。

a, b = gets.split.map{|n| (n.to_i + 12) % 14}
puts ["Draw", "Alice", "Bob"][a <=> b]

a <=> bの判定によって下記のようになる。

  • aの数字が大きい→ 1 →配列["Draw", "Alice", "Bob"]の2番目
  • bの数字が大きい→ -1 →配列["Draw", "Alice", "Bob"]の最後
  • aとbが等しい → 0 →配列["Draw", "Alice", "Bob"]の1番目

なるほど…!!!

●出てきたメソッド等

公式リファレンスを見る訓練。
昨日も同じメモをした涙、宇宙船演算子<=>

■振り返りなど

A問題としては結構難しくて時間がかかってしまった。。。とはいえ全部わかる内容だったので、自力でたどり着けるようになりたい。