こんにちは!じゃいごテックのあつしです。
今回はpaiza Cランクレベルアップメニュー から文字列という問題を解説していきたいと思います。
この問題集は文字列に関する6個のSTEP問題(D級)とFINAL問題(C級)で構成されていて、STEP問題を解いて行けばFINAL問題も解けるはず!となっています。
STEP問題を解いてみる
STEP問題は文字列の基本問題です。
簡単な解説は付けていますが、難しいと感じたら下記の記事も参考にしてみて下さい。
STEP1: 整数と文字列 (paizaランク D 相当)
STEP1 は1行目のデータ件数n
、続くn行の文字列s
を次々受け取り、文字列の長さを出力していく問題です。
解答例
<<EOS 入力例1 2 10 100 出力例1 2 3 入力例2 3 1234 0 99 出力例2 4 1 2 EOS # [解答例1] n = gets.to_i n.times do s = gets.chomp puts s.length end # [解答例2] gets.to_i.times { puts gets.chomp.length }
解答例1では、データ件数n
を受け取り、n回の繰り返し処理を行っています。繰り返し処理の中で文字列s
を受け取って、chompメソッド
、lengthメソッド
で末尾の改行を除いた文字列sの長さを取得し、putsメソッド
で表示と改行を行っています。
解答例2は、メソッドチェーンを使って、解答例1の処理を1行で実装しています。
STEP2: 部分文字列 (paizaランク D 相当)
STEP2 は1行目の文字a
、2行目の文字列s
を受け取り、文字列s
に文字a
が含まれているなら"YES"、含まれていないなら"NO"を出力するという問題です。
解答例
<<EOS 入力例1 Z Kirishima 出力例1 NO 入力例2 a paiza 出力例 YES EOS # [解答例1] a = gets.chomp s = gets.chomp if s.include?(a) puts "YES" else puts "NO" end # [解答例2] a = gets.chomp s = gets.chomp result = "NO" s.length.times do |idx| if s[idx] == a result = "YES" break end end puts result
解答例1ではinclude?メソッド
で文字列s
に文字a
が含まれているか?を調べて、結果がtrueなら"YES"
、falseなら"NO"
を出力しています。
解答例2では、文字a
、文字列s
を受け取り、sの先頭文字から順番にaと一致するかを次々と調べています。もし一致した場合はその後の調査は不要なので、break
で繰り返し処理を抜けて"YES"
を出力、最後まで一致しなかった場合は"NO"
を出力しています。
STEP3: 数字の文字列操作(基本) (paizaランク D 相当)
STEP3 は4桁の整数の文字列s
を受け取り、1番目と4番目の数を足したものと、2番目と3番目の数を足したものを文字列として連結して出力する問題です。
<<EOS 入力例1 2134 出力例1 64 入力例2 0920 出力例2 011 EOS # [解答例1] s = gets.chomp a = (s[0].to_i + s[3].to_i).to_s b = (s[1].to_i + s[2].to_i).to_s puts a + b # [解答例2] ary = gets.split("").map(&:to_i) a = (ary[0] + ary[3]).to_s b = (ary[1] + ary[2]).to_s puts a + b
この問題のポイントは数値の足し算と文字列の足し算(連結)を使い分けることです。
解答例1では、入力を文字列のまま受け取り、1番目と4番目、2番目と3番目の文字を整数に変換してから足したものを再度文字列に変換し、連結して出力しています。
解答例2では、入力をsplit("")
で入力文字列を1文字に分割(charsメソッド
でもOK)、各要素を整数に変換して配列ary
に格納し、配列の1番目と4番目、2番目と3番目の数を足したものをそれぞれ文字列に変換してから連結して出力しています。
STEP4: 数字の文字列操作(0埋め) (paizaランク D 相当)
STEP4 は最大3桁の整数の文字列n
を受け取り、3桁未満の場合は3桁になるように"0"で埋めて出力する問題です。
解答例
<<EOS 入力例1 7 出力例1 007 入力例2 123 出力例2 123 EOS # [解答例1] n = gets.chomp zero = "0" * (3 - n.length) puts zero + n # [解答例2] puts gets.chomp.rjust(3, "0") # [解答例3] puts sprintf("%03d", gets.to_i) # [解答例4] puts "%03d" % gets.to_i
この問題のポイントは文字列の長さから追加する0の個数を求めることです。解答例2~4のメソッドで解くことも出来ます。
解答例1は、整数の文字列n
を受け取り、"0" * (桁数3 - nの文字長)
で追加する"0"の数を求め、文字列n
と連結して出力しています。
解答例2は、rjustメソッド
で文字長を3、文字長に満たない時に埋める文字を"0"で出力しています。
解答例3は、sprintfメソッド
で、0埋め3桁の文字列を生成して出力しています。(printf
も使えます)
解答例4は、%メソッド
で、0埋め3桁の文字列を生成して出力しています。
STEP5: 数字の文字列操作(時刻1) (paizaランク D 相当)
STEP5 はhh:mmの形式
で時刻データが与えられるので、":"
で時間と分を分割し、0埋めなしの改行区切りで出力する問題です。
解答例
<<EOS 入力例1 12:34 出力例 12 34 入力例2 01:03 出力例2 1 3 EOS # [解答例1] time = gets.split(":").map(&:to_i) puts time # [解答例2] h, m = gets.split(":").map(&:to_i) time = Time.local(2021, 1, 1, h, m) puts [time.hour, time.min]
解答例1は、入力を":"
で分割して整数に変換したものを、配列time
に格納し、putsメソッド
で要素を改行区切りで出力しています。
解答例2は、入力を":"
で分割して整数に変換して時間h
、分m
を取り出し、h, mを使ってTimeクラスのインスタンスtimeを生成して、time.hour
、time.min
を出力しています。
STEP6: 数字の文字列操作(時刻2) (paizaランク D 相当)
STEP6 はhh:mm形式
で00:00~23:59
の時刻データが与えられ、30分後の時刻を計算して出力する問題です。
<<EOS 入力例1 01:02 出力例1 01:32 入力例2 12:31 出力例2 13:01 EOS # [解答例1] h, m = gets.split(":").map(&:to_i) # m に 30 を加えて60 以上なら繰り上がり処理 m += 30 if m >= 60 m -= 60 h += 1 end # h が 24 なら 0 にする if h == 24 h -= 24 end # h, m を文字列に変換して文字数が 1 なら先頭に "0" を追加 h = h.to_s h = "0" << h if h.length == 1 m = m.to_s m = "0" << m if m.length == 1 # hh:mmの形式で出力する puts "#{h}:#{m}" # [解答例2] h, m = gets.split(":").map(&:to_i) # Timeクラスのインスタンスを生成 # Time.local(year = 2021, mon = 1, day = 1, hour = h, min = m [, sec = 0 ,usec = 0]) time = Time.local(2021, 1, 1, h, m) # 30分 -> 1800秒に換算して time に加算 time += 60 * 30 # strftimeメソッドで hh:mm 形式の文字列を生成して出力 puts time.strftime("%H:%M")
解答例1は、入力データを時間h
と分m
に分割して整数に変換した後、m
に30を加えて、繰り上がり処理を行い、計算結果を指定の文字列形式に変換して出力しています。
解答例2は、入力データを時間h
と分m
に分割して整数に変換した後、2021年1月1日h時m分0秒0マイクロ秒でTimeクラスのインスタンスtimeを生成し、時刻の足し算(※)を行い、strftimeメソッド
で指定の文字列形式に変換して出力しています。
※ Timeクラスは時刻の計算が出来る
文字列 (paizaランク C 相当) を解いてみる
※ paiza Cランクレベルアップメニューより
問題
パイザ君の家の前では毎週日曜日に工事が行われます。この先 N 週間、工事が日曜日の何時に始まり、どれくらいの時間続くのかは分かっています。パイザ君は工事の間は家を離れようと思っているので、それぞれの日に工事が何時に終わるのかを知りたいと思いました。
工事が N 週間続くとして、各週日曜日の工事が始まる時刻と、工事が何時間何分続くのかに関する情報が与えられるので、工事が終わる時刻を 00:00 から 23:59 までの 24 時間表記で出力してください(ここで「工事が終わる時刻」とは、工事が h 時間 m 分続くとした場合、工事が始まった時刻の h 時間 m 分後を指します)。
入力される値
入力は以下のフォーマットで与えられます。
N t_1 h_1 m_1 ... t_N h_N m_N
1 行目には工事が続く週の数を表す正整数 N が与えられ、 2 行目から (N + 1) 行目には工事が始まる時刻と、工事がどれくらい続くのかについての情報が、 "t_i h_i m_i" という形式で与えられます(1 ≤ i ≤ N)。これは時刻 t_i に工事が始まり、 h_i 時間 m_i 分工事が続くことを意味しています。t_i, h_i, m_i は各行において半角スペース区切りで与えられます。
t_i は 24 時間表記で時刻を表す文字列で、 "AB:XY" という形をしており、これは AB 時 XY 分を表します。ただし、今回は 00:00 から 23:59 までの 24 時間表記を採用し、時・分を表す数字が 1 桁の場合には十の位を 0 で埋めます。
入力値最終行の末尾に改行が1つ入ります。
期待する出力
入力された通りの順番で、各週の工事が終わる時刻を N 行出力してください。時刻の表記は入力と同じフォーマットに従うものとし、 24 時以降は翌日の時刻を記し、 00:00 から 23:59 までの間に収まるように、また、時や分を表す数字が1桁の場合には十の位を 0 で埋めてください。たとえば 24 時は 00:00、 27 時は 03:00 となります。
末尾に改行を入れ、余計な文字、空行を含んではいけません。
条件
すべてのテストケースにおいて、以下の条件をみたします。
- 1 ≤ N ≤ 50
- 0 ≤ h_i ≤ 23
- 0 ≤ m_i ≤ 59
- 入力例1
- 1
13:00 1 30
- 出力例1
- 14:30
- 入力例2
- 2
15:59 0 1
23:20 1 0
- 出力例2
- 16:00
00:20
攻略ポイント
ポイント
- 標準入力からの複数行データ取得
- 文字列の分割、連結
- 文字列型 ⇔ 整数型 の型変換
- 時刻の計算(24進数、60進数)
- 0埋めの処理
デバッグを楽にするためにメソッドを作成します。メソッドについては下記の記事も参考にしてみて下さい。
問題を解く流れ
入出力例をコピペしてヒアドキュメントで変数に代入し、データを確認します。正しく受け取れていれば確認用コードは削除します。
INPUT1 = <<~"EOS" 1 13:00 1 30 EOS OUTPUT1 = <<~"EOS" 14:30 EOS INPUT2 = <<~"EOS" 2 15:59 0 1 23:20 1 0 EOS OUTPUT2 = <<~"EOS" 16:00 00:20 EOS # 確認用コード p INPUT1 # > "1\n13:00 1 30\n" p INPUT2 # > "2\n15:59 0 1\n23:20 1 0\n"
続いて問題を解くメソッド( solve
メソッドとします)を変数の下に定義します。
まず、入力データを受け取る処理を書きます。
入力された文字列を改行で分割し、1行目は使わないので _
に代入、2行目以降を配列 schedules
に代入します。
def solve(input_lines) # input_lines を改行区切りで分割する # 1行目を _ に代入(使わないので捨てる) # 2行目以降を配列 schedules に格納 _, *schedules = input_lines.split("\n") # 確認用コード schedules end # 確認用コード p solve(INPUT1) # > ["13:00 1 30"] p solve(INPUT2) # > ["15:59 0 1", "23:20 1 0"]
開始時刻と所要時間のデータが正しく受け取れていれば、solve
メソッドに終了時刻を計算する処理を追加していきます。
schedules
の先頭から次々と要素を取り出してschedule
に代入します。schedule
の3番目の文字":"
を半角スペースに置き換えます。schedule
を半角スペースで分割・各要素を整数に変換し、開始の時間s_h
,開始の分s_m
,工事の時間w_h
,工事の分w_m
に代入します。- 開始時刻に工事の所要時間を加算して
終了の時間f_h
,終了の分f_m
を求めます。 - 終了時刻(
f_h
,h_m
)からhh:mm形式
の文字列を生成します。 mapメソッド
を使って終了時刻を計算した結果をresult
に代入します。
def solve(input_lines) # input_lines を改行区切りで分割する # 1行目を _ に代入(使わないので捨てる) # 2行目以降を配列 schedules に格納 _, *schedules = input_lines.split("\n") result = schedules.map do |schedule| # 3文字目 ":" を半角スペースに変更 schedule[2] = " " s_h, s_m, w_h, w_m = schedule.split.map(&:to_i) # 【終了時刻の計算】 # 分の計算 f_m = s_m + w_m # 繰り上がりがある場合 if f_m >= 60 f_m -= 60 w_h += 1 end # 時間の計算 f_h = s_h + w_h # 繰り上がりがある場合 if f_h >= 24 f_h -= 24 end # 【 "0"埋め処理 】 # 時間を文字列に変換する f_h = f_h.to_s # 文字数が 1 なら 先頭に "0" を加えて埋め 2桁 にする f_h = "0" << f_h if f_h.length == 1 # 分を文字列に変換する f_m = f_m.to_s # 文字数が 1 なら 先頭に "0" を加えて埋め 2桁 にする f_m = "0" << f_m if f_m.length == 1 # hh:mm 形式の文字列を返す "#{f_h}:#{f_m}" end # 確認用コード result end # 確認用コード p solve(INPUT1) # > ["14:30"] p solve(INPUT2) # > ["16:00", "00:20"]
終了時刻が正しく計算されていれば、あとは出力形式を整えれば完成です。
p solve(INPUT1)
を puts solve(STDIN.read)
に変更すれば正解となりますが、p solve(INPUT1) == OUTPUT1 -> true
を確認するために、結果を改行区切りの文字列に変換して、末尾にも改行を加えます。
def solve(input_lines) # input_lines を改行区切りで分割する # 1行目を _ に代入(使わないので捨てる) # 2行目以降を配列 schedules に格納 _, *schedules = input_lines.split("\n") result = schedules.map do |schedule| # 3文字目 ":" を半角スペースに変更 schedule[2] = " " s_h, s_m, w_h, w_m = schedule.split.map(&:to_i) # 【終了時刻の計算】 # 分の計算 f_m = s_m + w_m # 繰り上がりがある場合 if f_m >= 60 f_m -= 60 w_h += 1 end # 時間の計算 f_h = s_h + w_h # 繰り上がりがある場合 if f_h >= 24 f_h -= 24 end # 【 "0"埋め処理 】 # 時間を文字列に変換する f_h = f_h.to_s # 文字数が 1 なら 先頭に "0" を加えて埋め 2桁 にする f_h = "0" << f_h if f_h.length == 1 # 分を文字列に変換する f_m = f_m.to_s # 文字数が 1 なら 先頭に "0" を加えて埋め 2桁 にする f_m = "0" << f_m if f_m.length == 1 # hh:mm 形式の文字列を返す "#{f_h}:#{f_m}" end # 処理結果を改行で連結して末尾に改行を加える result.join("\n") << "\n" end # 確認用コード p solve(INPUT1) # > "14:30\n" p solve(INPUT1) == OUTPUT1 # > true p solve(INPUT2) # > "16:00\n00:20\n" p solve(INPUT2) == OUTPUT2 # > true
確認用コードを標準入力からのデータ受け取りに変更して、動作確認をしたら提出します。
複数行のデータ受け取りなので STDIN.read
を使います。(入力終了は Ctrl+D
又は Ctrl+Z
)
解答コード
def solve(input_lines) # input_lines を改行区切りで分割する # 1行目を _ に代入(使わないので捨てる) # 2行目以降を配列 schedules に格納 _, *schedules = input_lines.split("\n") result = schedules.map do |schedule| # 3文字目 ":" を半角スペースに変更 schedule[2] = " " s_h, s_m, w_h, w_m = schedule.split.map(&:to_i) # 【終了時刻の計算】 # 分の計算 f_m = s_m + w_m # 繰り上がりがある場合 if f_m >= 60 f_m -= 60 w_h += 1 end # 時間の計算 f_h = s_h + w_h # 繰り上がりがある場合 if f_h >= 24 f_h -= 24 end # 【 "0"埋め処理 】 # 時間を文字列に変換する f_h = f_h.to_s # 文字数が 1 なら 先頭に "0" を加えて埋め 2桁 にする f_h = "0" << f_h if f_h.length == 1 # 分を文字列に変換する f_m = f_m.to_s # 文字数が 1 なら 先頭に "0" を加えて埋め 2桁 にする f_m = "0" << f_m if f_m.length == 1 # hh:mm 形式の文字列を返す "#{f_h}:#{f_m}" end # 処理結果を改行で連結して末尾に改行を加える result.join("\n") << "\n" end puts solve(STDIN.read)
他の解答例
- splitで分割したい文字が複数ある場合は正規表現を使うと簡単に書けます。
- Timeクラス
- 秒の単位で時刻の加減算が出来ます。
- Time.strftimeメソッドで任意のフォーマットで文字列に変換出来ます。
def solve(input_lines) _, *schedules = input_lines.split("\n") result = schedules.map do |schedule| # 正規表現を使って ":" と " " で分割する s_h, s_m, w_h, w_m = schedule.split(/[: ]/).map(&:to_i) # 開始時刻で Time クラスのインスタンスを生成する(時・分以外の情報はダミー) start_time = Time.local(Time.now.year, 1, 1, s_h, s_m) # 所要時間を秒に換算して開始時刻に加算する finish_time = start_time + 3600 * w_h + 60 * w_m # strftimeメソッドで "hh:mm" 形式の文字列を返す finish_time.strftime("%H:%M") end # 終了時刻の配列を改行区切りで連結して末尾に改行を追加する result.join("\n") << "\n" end puts solve(STDIN.read)
今回のまとめ
この問題集を通して以下の操作を覚えることが出来ました。
複数行のデータの受け取り
文字列の分割、結合
文字列型⇔整数型の変換
文字長のカウント
部分文字列の探索
繰り返し処理
条件分岐
また、n進数の計算が出来るようになりました。
標準入出力でよく使う文字列の分割・結合に加えて、文字長のカウントや部分文字列の探索などを扱いました。
今回は、時刻の繰り上がりと0埋めを実装する問題でしたが、日付・時刻を扱うときにTimeクラスが便利なので参考としてご紹介しました。