paiza プログラミング

[Ruby]paiza Cランクレベルアップメニュー 文字列 (paizaランク C 相当)の解説

paiza解説_Cランクレベルアップ_文字列

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

今回は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.hourtime.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クラスが便利なので参考としてご紹介しました。



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


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






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







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







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







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


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

© 2024 じゃいごテック