こんにちは!じゃいごテックのあつしです。
今回はpaiza Cランクレベルアップメニュー からforループという問題を解説します。
この問題集は繰り返し処理に関する4個のSTEP問題(D級)とFINAL問題(C級)で構成されていて、STEP問題を解いて行けばFINAL問題も解けるはず!となっています。
普段、Rubyでfor文を使うことはありませんが、今回は問題の意図に沿ってfor文を使って解説してみます。
STEP問題を解いてみる
STEP問題は繰り返し処理の基本問題です。
簡単な解説は付けていますが、難しいと感じたら下記の記事も参考にしてみて下さい。
STEP1: 3の倍数のカウント (paizaランク D 相当)
STEP1 はn個の半角区切りの整数を受け取り、与えられた整数のうちで3の倍数が何個あるかを出力する問題です。
解答例
<<EOS
入力例1
2
4 3
出力例
1
入力例2
3
1 6 9
出力例2
2
EOS
# [解答例1]
n = gets.to_i
ary = gets.split.map(&:to_i)
result = 0
for idx in 0...n
# 3の倍数なら result に +1 する
result += 1 if ary[idx] % 3 == 0
end
puts result
# [解答例2]
_ = gets
ary = gets.split.map(&:to_i)
puts gets.split.count { |num| num.to_i % 3 == 0 }
解答例1は、整数を一旦全て受け取って配列aryに格納し、forループで配列aryの先頭から次々参照して、3の倍数ならカウントアップしています。
解答例2は、整数を一旦全て受け取って配列aryに格納し、countメソッドを使って、3倍数をカウントしています。
STEP2: フラグ管理 (paizaランク D 相当)
STEP2 はn個の改行区切りの整数を受け取り、その中に 7 が一つでもあれば"YES"、なければ"NO"を出力する問題です。
解答例
<<EOS
入力例1
2
7
1
出力例1
YES
入力例1
3
4
11
35
出力例2
NO
EOS
# [解答例1]
n = gets.to_i
flag = false
for i in 0...n
if gets.to_i == 7
flag = true
break
end
end
if flag
puts "YES"
else
puts "NO"
end
# [解答例2]
n = gets.to_i
ary = n.times.map { gets.to_i }
puts ary.include?(7) ? "YES" : "NO"
解答例1は、flag=falseで初期化してから、forループでn回の繰り返し処理を行い、その中で整数を受け取って7かを調べ、7ならflag=trueにしてループを抜けます。
ループを抜けたらflagの状態を調べて"YES"、または"NO"を出力します。
解答例2は、n個の整数を全て受け取って配列aryに格納してから、include?メソッドで7が一つでも含まれているかを調べています。(any?メソッドでも可)
STEP3: インデックス取得 (paizaランク D 相当)
STEP3 はn個の改行区切りの整数を受け取り、指定された整数kに最初に一致するのは何番目かを出力する問題です。
<<EOS
入力例1
2
5
6
6
出力例1
2
入力例2
4
9
8
2
2
2
出力例2
3
EOS
# [解答例1]
n = gets.to_i
for idx in 0...n
num = gets.to_i
if num == k
puts idx + 1
break
end
end
# [解答例2]
n = gets.to_i
ary = n.times.map { gets.to_i }
k = gets.to_i
puts ary.index(k) + 1
解答例1では、forループでidxを0からn-1まで増やしながら、n回の繰り返し処理を行い、その中で整数を受け取って整数kと一致するかを調べ、一致したらidx+1(何番目か)を出力してループを抜けています。
解答例2では、n個の整数を全て受けとって配列aryに格納してから、indexメソッドで最初に整数kと一致する要素のインデックスを調べて、インデックスに1を加えて出力しています。
※このコードだと与えられた整数の中に整数kがなかった場合にエラーが発生しますが、必ず一致する整数が1個以上あることがわかっています。
STEP4: 多重ループ (paizaランク D 相当)
STEP4 m個の文字とn個の文字列があたえられ、文字順の文字列順の全組み合わせについて、文字cが文字列sの部分文字列かを調べて"YES"か"NO"を出力する問題です。
調べる順番
文字c_1は文字列s_1の部分文字列か?
文字c_1は文字列s_2の部分文字列か?
文字c_1は文字列s_3の部分文字列か?
・
・
文字c_2は文字列s_1の部分文字列か?
文字c_2は文字列s_2の部分文字列か?
文字c_2は文字列s_3の部分文字列か?
・
・
文字c_mは文字列s_nの部分文字列か?
解答例
<<EOS
入力例1
1
a
2
paiza
kyoko
出力例1
YES
NO
入力例2
2
c
d
2
cat
dog
出力例2
YES
NO
NO
YES
EOS
# [解答例1]
m = gets.to_i
ary_c = []
for _ in 0...m
ary_c << gets.chomp
end
n = gets.to_i
ary_s = []
for _ in 0...n
ary_s << gets.chomp
end
for c_i in 0...m
for s_i in 0...n
flag = false
word = ary_s[s_i]
for w_i in 0...word.length
if ary_c[c_i] == word[w_i]
flag = true
break
end
end
if flag
puts "YES"
else
puts "NO"
end
end
end
# [解答例2]
m = gets.to_i
ary_c = m.times.map { gets.chomp }
n = gets.to_i
ary_s = n.times.map { gets.chomp }
ary_c.each do |char|
ary_s.each do |word|
puts word.include?(char) ? "YES" : "NO"
end
end
解答例1は、文字を参照するforループの中に文字列を参照するforループを設定して順番を決め、参照した文字列の先頭文字から順に文字と一致するかを調べています。
解答例2は、文字を参照するeachループの中に文字列を参照するeachループを設定して順番を決め、include?メソッドで参照した文字・文字列が部分一致かを調べています。
forループ (paizaランク C 相当) を解いてみる
※ paiza Cランクレベルアップメニューより
問題
パイザ君の所属する会社では忘年会の余興で次のようなゲームを行います。まず、正整数 M が発表され、参加者は手元の紙に M 個、好きな数字を書きます。このとき、紙に書く数のそれぞれは 1 以上 M 以下であり、同じ数字を何度書いても構いません。その後、 1 以上 M 以下の数 K が発表され、各参加者は自分の紙に数字 K を書いた数だけポイントをもらい、ポイントの高い順に景品が配られます。
忘年会の参加者の人数 N と、数 M , K が与えられ、各参加者が書いた紙が与えられるので、それぞれの参加者の得点を計算して出力してください。
入力される値
入力は以下のフォーマットで与えられます。
N M K
a_{1,1} ... a_{1,M}
...
a_{N,1} ... a_{N,M}
1 行目には上で説明した数 N, M, K が半角スペース区切りで与えられ、 2 行目から (N + 1) 行目までには各参加者が紙に書いた数字が M 個ずつ半角スペース区切りで、 N 回与えられます。
入力値最終行の末尾に改行が1つ入ります。
期待する出力
入力された通りの順番で、各参加者の得点をN回、改行区切りで出力してください。
末尾に改行を入れ、余計な文字、空行を含んではいけません。
条件
すべてのテストケースにおいて、以下の条件をみたします。
- 1 ≤ N , M ≤ 50
- 1 ≤ K , a_{i,j} ≤ M(1 ≤ i ≤ N , 1 ≤ j ≤ M)
- 入力例1
- 3 2 1
2 2
1 2
1 1
- 出力例1
- 0
1
2
- 入力例2
- 4 5 2
1 3 4 4 5
2 2 2 2 2
1 2 3 4 5
1 1 1 1 1
- 出力例2
- 0
5
1
0
攻略ポイント
ポイント
- 標準入力からの複数行データ取得
- 文字列の分割、連結
- 文字列型 ⇔ 整数型 の型変換
- 二次元配列
- 多重ループ
デバッグを楽にするためにメソッドを作成します。メソッドについては下記の記事も参考にしてみて下さい。
問題を解く流れ
入出力例をコピペしてヒアドキュメントで変数に代入し、データを確認します。正しく受け取れていれば確認用コードは削除します。
INPUT1 = <<~"EOS" 3 2 1 2 2 1 2 1 1 EOS OUTPUT1 = <<~"EOS" 0 1 2 EOS INPUT2 = <<~"EOS" 4 5 2 1 3 4 4 5 2 2 2 2 2 1 2 3 4 5 1 1 1 1 1 EOS OUTPUT2 = <<~"EOS" 0 5 1 0 EOS # 確認用コード p INPUT1 # > "3 2 1\n2 2\n1 2\n1 1\n" p INPUT2 # > "4 5 2\n1 3 4 4 5\n2 2 2 2 2\n1 2 3 4 5\n1 1 1 1 1\n"
続いて問題を解くメソッド( solve メソッドとします)を変数の下に定義します。
まず、入力データを受け取る処理を書きます。
入力された文字列を改行で分割し、1行目を半角スペースで分割して整数に変換し、n, m, k に代入、続くn行を半角スペースで分割した配列を配列ary に代入します。(二次元配列になります)
def solve(input_lines)
input_lines = input_lines.split("\n")
n, m, k = input_lines.shift.split.map(&:to_i)
ary = []
for i in 0...n
ary << input_lines[i].split.map(&:to_i)
end
# 確認用コード
[n, m, k, ary]
end
# 確認用コード
p solve(INPUT1)
# > [3, 2, 1, [[2, 2], [1, 2], [1, 1]]]
p solve(INPUT2)
# > [4, 5, 2, [[1, 3, 4, 4, 5], [2, 2, 2, 2, 2], [1, 2, 3, 4, 5], [1, 1, 1, 1, 1]]]
n, m, kとaryのデータが正しく受け取れていれば、solve メソッドにゲームの得点を計算する処理を追加していきます。
参加者を参照するforループの中に書かれた番号を参照するforループを設定します。- 書かれた番号と当たり番号が一致したら得点を+1します。
- 番号を参照するループを抜けたら、現在の参加者の得点の集計完了なので、得点を
配列result末尾に追加します。
def solve(input_lines)
input_lines = input_lines.split("\n")
n, m, k = input_lines.shift.split.map(&:to_i)
ary = []
for i in 0...n
ary << input_lines[i].split.map(&:to_i)
end
result = []
# 参加者を参照するループ
for n_i in 0...n
score = 0
# 書かれた番号を参照するループ
for m_i in 0...m
# 書かれた番号が当たりなら score +1
score += 1 if ary[n_i][m_i] == k
end
# 得点を result 末尾に追加
result << score
end
# 確認用コード
result
end
# 確認用コード
p solve(INPUT1)
# > [0, 1, 2]
p solve(INPUT1) == OUTPUT1
# > false
p solve(INPUT2)
# > [0, 5, 1, 0]
p solve(INPUT2) == OUTPUT2
# > false
得点が正しく計算されていれば、あとは出力形式を整えれば完成です。
p solve(INPUT1) を puts solve(STDIN.read) に変更すれば正解となりますが、p solve(INPUT1) == OUTPUT1 -> true を確認するために、結果を改行区切りの文字列に変換して、末尾にも改行を加えます。
def solve(input_lines)
input_lines = input_lines.split("\n")
n, m, k = input_lines.shift.split.map(&:to_i)
ary = []
for i in 0...n
ary << input_lines[i].split.map(&:to_i)
end
result = []
# 参加者を参照するループ
for n_i in 0...n
score = 0
# 書かれた番号を参照するループ
for m_i in 0...m
# 書かれた番号が当たりなら score +1
score += 1 if ary[n_i][m_i] == k
end
# 得点を result 末尾に追加
result << score
end
# 処理結果を改行で連結して末尾に改行を加える
result.join("\n") << "\n"
end
# 確認用コード
p solve(INPUT1)
# > "0\n1\n2\n"
p solve(INPUT1) == OUTPUT1
# > true
p solve(INPUT2)
# > "0\n5\n1\n0\n"
p solve(INPUT2) == OUTPUT2
# > true
確認用コードを標準入力からのデータ受け取りに変更して、動作確認をしたら提出します。
複数行のデータ受け取りなので STDIN.read を使います。(入力終了は Ctrl+D 又は Ctrl+Z)
解答コード
def solve(input_lines)
input_lines = input_lines.split("\n")
n, m, k = input_lines.shift.split.map(&:to_i)
ary = []
for i in 0...n
ary << input_lines[i].split.map(&:to_i)
end
result = []
# 参加者を参照するループ
for n_i in 0...n
score = 0
# 書かれた番号を参照するループ
for m_i in 0...m
# 書かれた番号が当たりなら score +1
score += 1 if ary[n_i][m_i] == k
end
# 得点を result 末尾に追加
result << score
end
# 処理結果を改行で連結して末尾に改行を加える
result.join("\n") << "\n"
end
puts solve(STDIN.read)
他の解答例
def solve(input_lines)
input_lines = input_lines.split("\n")
n, m, k = input_lines.shift.split.map(&:to_i)
ary = input_lines.shift(n).map do |line|
line.split.map(&:to_i)
end
# 参加者(m個の番号が書かれた紙)を順に参照する
result = ary.each.map do |member|
# 得点を計算する
member.count { |num| num == k }
end
# 処理結果を改行で連結して末尾に改行を加える
result.join("\n") << "\n"
end
puts solve(STDIN.read)
今回のまとめ
この問題集を通して以下の操作を覚えることが出来ました。
- 複数行のデータの受け取り
- 文字列の分割、結合
- 文字列型⇔整数型の変換
- 部分文字列の探索
- 多次元配列
- 多重ループ
- 条件分岐

二次元配列を扱う問題は今後もどんどん出てきますので、少しずつ慣れていきましょう!