paiza プログラミング

[Ruby]paiza Bランクレベルアップメニュー 五目並べ (paizaランク B 相当)の解説

B-lvup_五目並べ

こんにちは!じゃいごテックのあつしです。

今回はpaiza Bランクレベルアップメニュー から五目並べという問題を解説します。

この問題集は二次元配列に関する5個のSTEP問題(D~C級)FINAL問題(B級)で構成されていて、STEP問題を解いて行けばFINAL問題も解けるはず!となっています。

STEP問題を解いてみる

STEP問題は基本的なメソッドについての問題です。
簡単な解説は付けていますが、難しいと感じたら下記の記事も参考にしてみて下さい。

※過去に出題された小問題が出てくることがありますが、チケットに余裕があれば復習のつもりで取り組んでみましょう。

STEP1: 文字列の出力 (paizaランク D 相当)

STEP1 は5行の文字列をそのまま出力する問題です。

解答例

<<"EOS"

入力例1
XXOXO
OXOXX
OOOOO
OXOX.
XOXXO

出力例1
XXOXO
OXOXX
OOOOO
OXOX.
XOXXO

入力例2
XXOXO
OXOXX
.OXXO
OXOO.
XXXXX

出力例2
XXOXO
OXOXX
.OXXO
OXOO.
XXXXX

入力例3
...X.
...X.
...X.
...X.
OOOO.

出力例3
...X.
...X.
...X.
...X.
OOOO.

EOS

# [解答例1]
N = 5
N.times { puts gets }

# [解答例2]
N = 5
ary = N.times.map { gets.chomp }
puts ary

解答例1 : 5行分の入力をそのまま出力しています。

解答例2 : 5行分の入力を配列aryに格納してから、先頭から順に出力しています。


STEP2: 五目並べ(1行) (paizaランク D 相当)

STEP2 は5文字で3種類の文字(”O” か " X" か "." の3種類)の文字が与えられ、"O"か"X"が5個ならその文字、それ以外なら"D"を出力する問題です。

解答例

<<"EOS"

入力例1
OOOOO

出力例1
O

入力例2
OXOOO

出力例2
D

入力例3
...O.

出力例3
D

EOS

# [解答例1]
s = gets.chomp

o, x = 0, 0
(0..s.length - 1).each do |idx|
  if s[idx] == "O"
    o += 1
  elsif s[idx] == "X"
    x += 1
  end
end

if o == 5
  puts "O"
elsif x == 5
  puts "X"
else
  puts "D"
end

# [解答例2]
s = gets.chomp

if s.count("O") == 5
  puts "O"
elsif s.count("X") == 5
  puts "X"
else
  puts "D"
end

解答例1 : 文字列を先頭から参照して、"O"と"X"の集計を行い、どちらかが5個ならその文字を、それ以外なら"D"を出力しています。

解答例2 : countメソッドで"O"が5個か、"X"が5個かを判定して、どちらかが5個ならその文字を、それ以外なら"D"を出力しています。


STEP3: 五目並べ(横) (paizaランク C 相当)

STEP3 は5行5文字3種類("O"か"X"か".")で構成された文字列が与えられ、横の並び(各行)について調べて、"O"か"X"が5個あればその文字を、それ以外なら"D"を出力する問題です。

解答例

INPUT1 = <<~"EOS"
  XXOXO
  OXOXX
  OOOOO
  OXOX.
  XOXXO
EOS
OUTPUT1 = <<~"EOS"
  O
EOS

INPUT2 = <<~"EOS"
  XXOXO
  OXOXX
  .OXXO
  OXOO.
  XXXXX
EOS
OUTPUT2 = <<~"EOS"
  X
EOS

INPUT3 = <<~"EOS"
  O.O.X
  OXX.X
  O.X.X
  OO..X
  X.XOX
EOS
OUTPUT3 = <<~"EOS"
  D
EOS

# [解答例1]
N = 5
h_lines = N.times.map { gets.chomp }

result = "D"
h_lines.each do |line|
  if line.count("O") == 5
    result = "O"
    break
  elsif line.count("X") == 5
    result = "X"
    break
  end
end

puts result

# [解答例2]
N = 5
h_lines = N.times.map { gets.chomp.chars }
result = "D"
h_lines.each do |line|
  if line.count("O") == 5
    result = "O"
    break
  elsif line.count("X") == 5
    result = "X"
    break
  end
end

puts result

# [解答例3]
def solve(input_lines)
  h_lines = input_lines.split("\n").map(&:chars)

  n = h_lines.length
  result = "D"
  h_lines.each do |line|
    if line.count("O") == n
      result = "O"
      break
    elsif line.count("X") == n
      result = "X"
      break
    end
  end
  result
end

puts solve(STDIN.read)

# [参考 解答例3の動作確認用コード]
# puts solve(INPUT1)
# puts solve(INPUT2)
# puts solve(INPUT3)

解答例1: 各行を文字列のまま、countメソッドで"O"か"X"が5個揃っているかを判定しています。

解答例2: 各行をcharsメソッドで文字の配列にして、二次元配列にしてからcountメソッドで"O"か"X"が5個揃っているかを判定しています。
※ 後の問題の、縦や斜めのラインを確認するのに都合が良いためです。

解答例3: 解答例2と同じ方法で解いていますが、メソッドを定義しています。
※ 入力データが多くなってくると、打ち込みが大変なのでこの方法が便利です。


STEP4: 五目並べ(縦) (paizaランク C 相当)

STEP4 は5行5文字3種類("O"か"X"か".")で構成された文字列が与えられ、縦の並びについて調べて、"O"か"X"が5個あればその文字を、それ以外なら"D"を出力する問題です。

解答例

INPUT1 = <<~"EOS"
  XXOXO
  OXOXX
  OOOOO
  OXOX.
  XOXXO
EOS
OUTPUT1 = <<~"EOS"
  D
EOS

INPUT2 = <<~"EOS"
  XXOXO
  OXOXX
  .OXXO
  OXOO.
  XXXXX
EOS
OUTPUT2 = <<~"EOS"
  D
EOS

INPUT3 = <<~"EOS"
  ...X.
  ...X.
  ...X.
  ...X.
  OOOO.
EOS
OUTPUT3 = <<~"EOS"
  D
EOS

# [解答例1]
N = 5
h_lines = N.times.map { gets.chomp.chars }

v_lines = []
(0..N - 1).each do |h|
  line = []
  (0..N - 1).each do |w|
    line.push(h_lines[w][h])
  end
  v_lines.push(line)
end

result = "D"
v_lines.each do |line|
  if line.count("O") == 5
    result = "O"
    break
  elsif line.count("X") == 5
    result = "X"
    break
  end
end

puts result

# [解答例2]
def solve(input_lines)
  h_lines = input_lines.split("\n").map(&:chars)
  n = h_lines.length

  v_lines = []
  (0..n - 1).each do |h|
    line = []
    (0..n - 1).each do |w|
      line.push(h_lines[w][h])
    end
    v_lines.push(line)
  end

  result = "D"
  v_lines.each do |line|
    if line.count("O") == n
      result = "O"
      break
    elsif line.count("X") == n
      result = "X"
      break
    end
  end
  result
end

puts solve(STDIN.read)

# [参考 確認用コード]
# puts solve(INPUT1)
# puts solve(INPUT2)
# puts solve(INPUT3)

# [解答例3]
def solve(input_lines)
  h_lines = input_lines.split("\n").map(&:chars)

  n = h_lines.length
  v_lines = h_lines.transpose
  result = "D"
  v_lines.each do |line|
    if line.count("O") == n
      result = "O"
      break
    elsif line.count("X") == n
      result = "X"
      break
    end
  end
  result
end

puts solve(STDIN.read)

# [参考 確認用コード]
# puts solve(INPUT1)
# puts solve(INPUT2)
# puts solve(INPUT3)

解答例1: eachメソッドで0から4のインデックスで2重ループを設定し、縦と横の並びを入れ替えた配列を作り、各ラインの文字について"O"か"X"が5個揃っているかを判定しています。

解答例2: 解答例1と同じ方法で解いていますが、メソッドを定義しています。
※ 入力データが多くなってくると、打ち込みが大変なのでこの方法が便利です。

解答例3: transposeメソッドを使って、縦と横の並びを入れ替えた配列を生成して、各ラインの文字について"O"か"X"が5個揃っているかを判定しています。


STEP5: 五目並べ(斜め) (paizaランク C 相当)

STEP5 は5行5文字3種類("O"か"X"か".")で構成された文字列が与えられ、斜めの並びについて調べて、"O"か"X"が5個あればその文字を、それ以外なら"D"を出力する問題です。

解答例

INPUT1 = <<~"EOS"
  XXOXO
  OXOXX
  OOOOO
  OXOX.
  XOXXO
EOS
OUTPUT1 = <<~"EOS"
  D
EOS

INPUT2 = <<~"EOS"
  XXOXO
  OXOXX
  .OXXO
  OXOO.
  XXXXX
EOS
OUTPUT2 = <<~"EOS"
  D
EOS

INPUT3 = <<~"EOS"
  ...X.
  ...X.
  ...X.
  ...X.
  OOOO.
EOS
OUTPUT3 = <<~"EOS"
  D
EOS

# [解答例]
def solve(input_lines)
  h_lines = input_lines.split("\n").map(&:chars)
  n = h_lines.length

  d_lines = [[], []]
  (0..n - 1).each do |i|
    d_lines[0].push(h_lines[i][i])
    d_lines[1].push(h_lines[n - 1 - i][i])
  end

  result = "D"
  d_lines.each do |line|
    if line.count("O") == n
      result = "O"
      break
    elsif line.count("X") == n
      result = "X"
      break
    end
  end
  result
end

puts solve(STDIN.read)

# [参考 確認用コード]
# puts solve(INPUT1)
# puts solve(INPUT2)
# puts solve(INPUT3)

解答例: eachメソッドで0から4のインデックスでループを設定して斜めの配列を二つ作り、各ラインの文字について"O"か"X"が5個揃っているかを判定しています。

  • 斜めライン1: ary[0][0], ary[1][1], ary[2][2], ary[3][3], ary[4][4]
  • 斜めライン2: ary[4][0], ary[3][1], ary[2][2], ary[1][3], ary[0][4]

五目並べ (paizaランク B 相当)を解いてみる

※ paiza Bランクレベルアップメニューより

問題

5行5列の五目並べの盤面が与えられます。

盤面の各マスには、"O"か"X"か"."が書かれています。

"O"と"X"は、それぞれプレイヤーの記号を表します。

同じ記号が縦か横か斜めに連続で5つ並んでいれば、その記号のプレイヤーが勝者となります。

勝者の記号を1行で表示してください。
勝者がいない場合は、引き分けとして、"D"を表示してください。

入力される値

入力は以下のフォーマットで与えられます。

s_1
s_2
s_3
s_4
s_5

入力値最終行の末尾に改行が1つ入ります。

期待する出力

勝者の記号を1行で表示してください。
勝者がいない場合は、引き分けとして、"D"を表示してください。

条件

すべてのテストケースにおいて、以下の条件をみたします。

  • s_iの文字数は5文字
  • s_iに含まれる文字は"O"か"X"か"."のいずれか
  • 勝者が2人になる盤面が与えられることはありません
入力例1
XXOXO
OXOXX
OOOOO
OXOX.
XOXXO
出力例1
O
入力例2
XXOXO
OXOXX
.OXXO
OXOO.
XXXXX
出力例2
X
入力例3
...X.
...X.
...X.
...X.
OOOO.
出力例3
D
攻略ポイント

ポイント

  • 二次元配列を扱う(縦・斜めにアクセスする)
  • 配列の要素を集計する
問題を解く流れ

入出力例をコピペしてヒアドキュメントで変数に代入し、データを確認します。正しく受け取れていれば確認用コードは削除します。

INPUT1 = <<~"EOS"
  XXOXO
  OXOXX
  OOOOO
  OXOX.
  XOXXO
EOS
OUTPUT1 = <<~"EOS"
  O
EOS

INPUT2 = <<~"EOS"
  XXOXO
  OXOXX
  .OXXO
  OXOO.
  XXXXX
EOS
OUTPUT2 = <<~"EOS"
  X
EOS

INPUT3 = <<~"EOS"
  ...X.
  ...X.
  ...X.
  ...X.
  OOOO.
EOS
OUTPUT3 = <<~"EOS"
  D
EOS

p INPUT1
# > "XXOXO\nOXOXX\nOOOOO\nOXOX.\nXOXXO\n"
p OUTPUT1
# > "O\n"
p INPUT2
# > "XXOXO\nOXOXX\n.OXXO\nOXOO.\nXXXXX\n"
p OUTPUT2
# > "X\n"
p INPUT3
# > "...X.\n...X.\n...X.\n...X.\nOOOO.\n"
p OUTPUT3
# > "D\n"

続いて問題を解くメソッド( solve メソッドとします)を変数の下に定義します。

まず、入力データを受け取る処理を書きます。

※ データ量が多い場合はppメソッドを使うといい感じに改行を入れて表示してくれます。

def solve(input_lines)
  # 入力データ受け取り
  # 横5ライン
  h_lines = input_lines.split("\n").map(&:chars)

  # 確認用コード
  h_lines
end

# 確認用コード
pp solve(INPUT1)
# > [["X", "X", "O", "X", "O"],
# >  ["O", "X", "O", "X", "X"],
# >  ["O", "O", "O", "O", "O"],
# >  ["O", "X", "O", "X", "."],
# >  ["X", "O", "X", "X", "O"]]
pp solve(INPUT2)
# > [["X", "X", "O", "X", "O"],
# >  ["O", "X", "O", "X", "X"],
# >  [".", "O", "X", "X", "O"],
# >  ["O", "X", "O", "O", "."],
# >  ["X", "X", "X", "X", "X"]]
pp solve(INPUT3)
# > [[".", ".", ".", "X", "."],
# >  [".", ".", ".", "X", "."],
# >  [".", ".", ".", "X", "."],
# >  [".", ".", ".", "X", "."],
# >  ["O", "O", "O", "O", "."]]

データが正しく受け取れていれば、solve メソッドに横5ライン、縦5ライン、斜め2ラインについて"O"と"X"の数を調べる処理を追加していきます。(STEP3~5をひとつにまとめる)

def solve(input_lines)
  # 入力データ受け取り
  # 横5ライン
  h_lines = input_lines.split("\n").map(&:chars)

  # 縦5ライン
  v_lines = h_lines.transpose

  # 斜め2ライン
  n = h_lines.length
  d_lines = [[], []]
  (0..n - 1).each do |i|
    d_lines[0].push(h_lines[i][i])
    d_lines[1].push(h_lines[n - 1 - i][i])
  end

  # 全ラインをまとめる
  all_lines = h_lines + v_lines + d_lines

  # 全ラインで勝者の判定を行う
  result = "D"
  all_lines.each do |line|
    if line.count("O") == n
      result = "O"
    elsif line.count("X") == n
      result = "X"
    end
    break if result != "D"
  end

  # 確認用コード
  result
end

# 確認用コード
p solve(INPUT1)
# > "O"
p solve(INPUT1) == OUTPUT1
# > false
p solve(INPUT2)
# > "X"
p solve(INPUT2) == OUTPUT2
# false
p solve(INPUT3)
# > "D"
p solve(INPUT3) == OUTPUT3
# > false

あとは出力形式を整えれば完成です。

p solve(INPUT1) == OUTPUT1 -> true を確認するために、判定結果の末尾に改行を加えます。

def solve(input_lines)
  # 入力データ受け取り
  # 横5ライン
  h_lines = input_lines.split("\n").map(&:chars)

  # 縦5ライン
  v_lines = h_lines.transpose

  # 斜め2ライン
  n = h_lines.length
  d_lines = [[], []]
  (0..n - 1).each do |i|
    d_lines[0].push(h_lines[i][i])
    d_lines[1].push(h_lines[n - 1 - i][i])
  end

  # 全ラインをまとめる
  all_lines = h_lines + v_lines + d_lines

  # 全ラインで勝者の判定を行う
  result = "D"
  all_lines.each do |line|
    if line.count("O") == n
      result = "O"
    elsif line.count("X") == n
      result = "X"
    end
    break if result != "D"
  end

  # 判定結果の末尾に改行を追加
  result << "\n"
end

# 確認用コード
p solve(INPUT1)
# > "O\n"
p solve(INPUT1) == OUTPUT1
# > true
p solve(INPUT2)
# > "X\n"
p solve(INPUT2) == OUTPUT2
# > true
p solve(INPUT3)
# > "D\n"
p solve(INPUT3) == OUTPUT3
# > true

入出力例での動作確認が出来ましたので、標準入力からのデータ受け取りに変更して、手動で動作確認をしたら提出します。
複数行のデータ受け取りなので STDIN.read を使います。(入力終了は Ctrl+D 又は Ctrl+Z)

解答コード
def solve(input_lines)
  # 入力データ受け取り
  # 横5ライン
  h_lines = input_lines.split("\n").map(&:chars)

  # 縦5ライン
  v_lines = h_lines.transpose

  # 斜め2ライン
  n = h_lines.length
  d_lines = [[], []]
  (0..n - 1).each do |i|
    d_lines[0].push(h_lines[i][i])
    d_lines[1].push(h_lines[n - 1 - i][i])
  end

  # 全ラインをまとめる
  all_lines = h_lines + v_lines + d_lines

  # 全ラインで勝者の判定を行う
  result = "D"
  all_lines.each do |line|
    if line.count("O") == n
      result = "O"
    elsif line.count("X") == n
      result = "X"
    end
    break if result != "D"
  end

  # 判定結果の末尾に改行を追加
  result << "\n"
end
他の解答例
  • 横・縦・斜めラインの配列を生成する処理をメソッドにまとめる
  • 勝者を判定する処理をメソッドにまとめる
# 盤面情報を与えると横・縦・斜めの12ラインを返す
def all_lines(board)
  n = board.length

  # 縦5ライン
  v_lines = board.transpose

  # 斜め2ライン
  d_lines = [[], []]
  (0..n - 1).each do |i|
    d_lines[0].push(board[i][i])
    d_lines[1].push(board[n - 1 - i][i])
  end

  # 横5ライン・縦5ライン・斜め2ラインを配列にして返す
  board + v_lines + d_lines
end

# ラインの情報を与えると勝者を判定する
def winner(lines)
  lines.each do |line|
    if line.count("O") == 5
      return "O"
    elsif line.count("X") == 5
      return "X"
    end
  end
  return "D"
end

def solve(input_lines)
  # 入力データ受け取り
  board = input_lines.split("\n").map(&:chars)

  # 横5ライン・縦5ライン・斜め2ラインの配列を取得
  all_lines = all_lines(board)

  # 各ラインを調べて勝者を判定する
  result = winner(all_lines)

  # 判定結果の末尾に改行を追加
  result << "\n"
end

puts solve(STDIN.read)

今回のまとめ

  • charsメソッドで文字列を文字に分割して配列を作りました
  •  二次元配列を扱いました
    • インデックスを使って縦や斜めにアクセスする
    • transposeメソッドで行と列を入れ替える(転置)
  • countメソッドで配列(文字列)の指定した要素の数をカウントしました
  • 処理を分割したメソッドを作ってみました

B級問題では二次元配列を扱う問題が沢山出てきます。transposeメソッドも時々使うので覚えておくと便利だと思います!



【PR】アルゴリズム学習でお世話になった本


アルゴリズム関連の技術書は大抵C/C++で書かれていますが、ある程度プログラムが出来れば、処理の流れは理解することが出来ます。






通称: 螺旋本。C++で解説されています。AOJ(Aizu Online Judge)を運営している会津大学の渡辺准教授が書いた本です。データ構造や計算量の内容から丁寧に書いてありますのでアルゴリズム学習の最初の参考書としてオススメです。







通称: 蟻本。C++で解説されています。競技プログラミング中級者の定番書と言われていて、競技プログラミングで利用できる色々なアルゴリズムを学ぶことが出来ます。かなり高度な内容も含まれているので1冊分を完全に理解するのにはかなりの時間がかかりそうですが、手元に置いて何度も読み返しています。







通称: チーター本。C#, C++, JAVAでTopcoderの問題を解説しています。初心者~中級者向けに書かれているので解説が非常に丁寧です。







Python3でアルゴリズムを解説している本です。講義スタイルの本で、図やフローチャートを使ってアルゴリズムとデータ構造についてしっかりと説明されています。代わりにコードは少なめです。Pythonコードが読めなくても十分理解できると思います。


-paiza, プログラミング
-, , , ,

© 2024 じゃいごテック