こんにちは!じゃいごテックのあつしです。
今回は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)
今回のまとめ
この問題集を通して以下の操作を覚えることが出来ました。
- 複数行のデータの受け取り
- 文字列の分割、結合
- 文字列型⇔整数型の変換
- 部分文字列の探索
- 多次元配列
- 多重ループ
- 条件分岐
二次元配列を扱う問題は今後もどんどん出てきますので、少しずつ慣れていきましょう!