こんにちは!じゃいごテックのあつしです。
今回はpaiza クラス・構造体メニュー から構造体の更新という問題を解説します。
この問題集はクラス・構造体に関する3個のSTEP問題(C級)とFINAL問題(C級)で構成されていて、STEP問題を解いて行けばFINAL問題も解けるはず!となっています。
STEP問題を解いてみる
※ RubyにはStruct(構造体クラス)がありますが、全て通常のクラスで解いていきます。
簡単な解説は付けていますが、難しいと感じたら下記の記事も参考にしてみて下さい。
クラスの基本
メソッドの定義と呼び出し
標準入力・標準出力
STEP1: 構造体の作成 (paizaランク C 相当)
STEP1 はn人
分の「名前・年齢・誕生日・出身地」を指定された形式で出力する問題です。
指定の出力形式
User{ nickname : 名前 old : 年齢 birth : 誕生日 state : 出身地 }
解答例
# Userクラスの定義 class User # 名前・年齢・誕生日・出身地をインスタンス変数に格納する def initialize(nickname, old, birth, state) @nickname = nickname @old = old @birth = birth @state = state end # 指定された形式でインスタンスの情報を出力する def info <<~"EOS" User{ nickname : #{@nickname} old : #{@old} birth : #{@birth} state : #{@state} } EOS end end # 問題を解く処理 def solve(input_data) # 入力データ受け取り _, *users = input_data.split("\n") # users の先頭から順にインスタンス化した配列で上書きする users.map! do |user| nickname, old, birth, state = user.split user = User.new(nickname, old.to_i, birth, state) end # users の先頭から順に infoメソッド を呼び出した結果を result に格納 result = users.map { |user| user.info } # 処理結果を連結した文字列を返す result.join end puts solve(STDIN.read) # [参考 動作確認用コード] # pp solve(INPUT1) # > "User{\n" + # > "nickname : koko\n" + # > "old : 23\n" + # > "birth : 04/10\n" + # > "state : tokyo\n" + # > "}\n" # p solve(INPUT1) == OUTPUT1 # > true # pp solve(INPUT2) # > "User{\n" + # > "nickname : mako\n" + # > "old : 13\n" + # > "birth : 08/08\n" + # > "state : nara\n" + # > "}\n" + # > "User{\n" + # > "nickname : megumi\n" + # > "old : 14\n" + # > "birth : 11/02\n" + # > "state : saitama\n" + # > "}\n" + # > "User{\n" + # > "nickname : taisei\n" + # > "old : 16\n" + # > "birth : 12/04\n" + # > "state : nagano\n" + # > "}\n" # p solve(INPUT2) == OUTPUT2 # > true
解答例:
Userクラス
- インスタンス変数
- 名前 @nickname : String
- 年齢 @old : Integer
- 誕生日 @birth : String
- 出身地 @state : String
- インスタンスメソッド
- info : 指定された形式でインスタンスの情報を返す
solveメソッドの処理内容
n人
分の情報を受け取り、Userクラス
でインスタンス化して配列users
に格納します。配列users
の先頭から順にinfoメソッド
を呼びだした結果を配列result
に格納します。配列result
の要素を連結した文字列を返します。
(infoメソッド
でヒアドキュメントを使っているので各要素の末尾に改行が入っています。)
STEP2: 構造体の検索 (paizaランク C 相当)
STEP2 はn人
分の「名前・年齢・誕生日・出身地」をまとめたインスタンスの配列から指定された年齢の生徒の名前を出力する問題です。(同じ年齢の生徒が複数いないことがわかっています。)
解答例
INPUT1 = <<~"EOS" 1 koko 23 04/10 tokyo 23 EOS OUTPUT1 = <<~"EOS" koko EOS INPUT2 = <<~"EOS" 3 mako 13 08/08 nara megumi 14 11/02 saitama taisei 16 12/04 nagano 14 EOS OUTPUT2 = <<~"EOS" megumi EOS class Student attr_reader :name, :old def initialize(name, old, birth, state) @name = name @old = old @birth = birth @state = state end end def solve(input_data) # 入力データ受け取り _, *students, k = input_data.split("\n") # 配列students を先頭から順にインスタンス化した配列で上書きする students.map! do |student| name, old, birth, state = student.split Student.new(name, old.to_i, birth, state) end # old == k を満たす生徒の名前を参照して末尾に改行を加える students.find { |student| student.old == k.to_i }.name << "\n" end puts solve(STDIN.read) # [参考 確認用コード] # > p solve(INPUT1) # > "koko\n" # > p solve(INPUT1) == OUTPUT1 # > true # > p solve(INPUT2) # > "megumi\n" # > p solve(INPUT2) == OUTPUT2 # > true
解答例:
Studentクラス
- インスタンス変数
- 名前 @name : String, 外部から参照可
- 年齢 @old : Integer, 外部から参照可
- 誕生日 @birth : String
- 出身地 @state : String
solveメソッドの処理内容
n人
分の情報を受け取り、Studentクラス
でインスタンス化して配列students
に格納します。findメソッド
で指定された年齢の生徒を検索してその生徒の名前の末尾に改行を追加して返します。
(指定された年齢の生徒が必ず1人存在することがわかっているので複数人や該当なしの処理は記述していません。)
STEP3: 構造体の整列 (paizaランク C 相当)
STEP3 はn人
分の「名前・年齢・誕生日・出身地」を年齢の昇順に並び替えて指定された形式(入力と同じ形式)で出力する問題です。
解答例
class Student attr_reader :old def initialize(name, old, birth, state) @name = name @old = old @birth = birth @state = state end def info [@name, @old, @birth, @state].join(" ") end end def solve(input_data) # 入力データ受け取り _, *students = input_data.split("\n") # students を先頭から順にインスタンス化した配列で上書きする students.map! do |student| name, old, birth, state = student.split Student.new(name, old.to_i, birth, state) end # students を年齢順で並び替える # 先頭から順に入力と同じ形式の文字列を配列にして result に格納する result = students.sort_by { |student| student.old }.map(&:info) # 生徒の情報を改行で連結して末尾に改行を加える result.join("\n") << "\n" end puts solve(STDIN.read) # [参考 確認用コード] # pp solve(INPUT1) # > "koko 23 04/10 tokyo\n" # p solve(INPUT1) == OUTPUT1 # > true # pp solve(INPUT2) # > "mako 13 08/08 nara\n" + # > "megumi 14 11/02 saitama\n" + # > "taisei 16 12/04 nagano\n" # p solve(INPUT2) == OUTPUT2 # > true
解答例:
Studentクラス
- インスタンス変数
- 名前 @name : String
- 年齢 @old : Integer, 外部から参照可
- 誕生日 @birth : String
- 出身地 @state : String
- インスタンスメソッド
- info : 名前, 年齢, 誕生日, 出身地 を半角スペースで連結した文字列を返す
solveメソッドの処理内容
n人
分の情報を受け取り、Studentクラス
でインスタンス化して配列students
に格納します。sort_byメソッド
で配列students
を年齢の昇順に並び替えて、先頭から順にinfoメソッド
を実行した結果をresult
に格納します。result
を半角スペースで連結して末尾に改行を追加して返します。
構造体の更新 (paizaランク C 相当)を解いてみる
※ paizaレベルアップ問題集 クラス・構造体メニューより
問題
クラスの学級委員である paiza 君は、クラスのみんなに次のような形式でアカウントの情報を送ってもらうよう依頼しました。
名前 年齢 誕生日 出身地
送ってもらったデータを使いやすいように整理したいと思った paiza 君はクラス全員分のデータを次の形式でまとめることにしました。
User{
nickname : 名前
old : 年齢
birth : 誕生日
state : 出身地
}
途中で名前が変わった際にいちいちデータを修正するのが面倒だと思ったあなたは、生徒の構造体と新しい名前を受け取り、その名前を修正する関数 changeName を作成し、それを用いて生徒の名前を更新することにしました。
クラスの人数と全員の情報、更新についての情報が与えられるので、入力に従って名前を更新した後のクラスのメンバーの情報を出力してください。
入力される値
入力は以下のフォーマットで与えられます。
N K
n_1 o_1 b_1 s_1
...
n_N o_N b_N s_N
a_1 nn_1
...
a_K nn_K
- 1 行目では、paiza君のクラスの人数 N と名前更新の回数 K が与えられます。
- 続く N 行のうち i 行目 (1 ≦ i ≦ N) では、 i 番の生徒の名前・年齢・誕生日・出身地を表す整数・文字列 n_i ,o_i ,b_i , s_i が順に半角スペース区切りで与えられます。
- 続く K 行では、名前を更新する生徒の番号 a_i と、その人の新しい名前 nn_i が空白区切りで与えられます。
入力値最終行の末尾に改行が1つ入ります。
期待する出力
n_1 o_1 b_1 s_1
...
n_N o_N b_N s_N
名前の更新を全て終えた後の各クラスメートの情報を生徒番号の小さい順に入力と同様の形式でまとめたものを出力してください。
条件
すべてのテストケースにおいて、以下の条件をみたします。
- 1 ≦ N , K ≦ 10
- n_i , b_i , nn_i (1 ≦ i ≦ N) は 1 文字以上 20 文字以下の文字列
- b_i (1 ≦ i ≦ N) はMM/DD 形式の文字列(例 1月2日 → 01/02 12月31日 → 12/31)
- 1 ≦ o_i ≦ 100
- 1 ≦ a_i ≦ N (1 ≦ i ≦ K)
- 入力例1
- 1 1
koko 23 04/10 tokyo
1 nana
- 出力例1
- nana 23 04/10 tokyo
- 入力例2
- 3 2
mako 13 08/08 nara
taisei 16 12/04 nagano
megumi 14 11/02 saitama
2 taihei
3 megu
- 出力例2
- mako 13 08/08 nara
taihei 16 12/04 nagano
megu 14 11/02 saitama
攻略ポイント
ポイント
- クラスを定義する
- 指定された形式でインスタンス変数を出力するインスタンスメソッドを定義する
- 名前を変更するインスタンスメソッドを定義する
問題を解く流れ
入出力例をコピペしてヒアドキュメントで変数に代入し、データを確認します。正しく受け取れていれば確認用コードは削除します。(データが多い時はppメソッド
を使うといい感じに出力してくれます)
INPUT1 = <<~"EOS" 1 1 koko 23 04/10 tokyo 1 nana EOS OUTPUT1 = <<~"EOS" nana 23 04/10 tokyo EOS INPUT2 = <<~"EOS" 3 2 mako 13 08/08 nara taisei 16 12/04 nagano megumi 14 11/02 saitama 2 taihei 3 megu EOS OUTPUT2 = <<~"EOS" mako 13 08/08 nara taihei 16 12/04 nagano megu 14 11/02 saitama EOS pp INPUT1 # > "1 1\n" + "koko 23 04/10 tokyo\n" + "1 nana\n" pp OUTPUT1 # > "nana 23 04/10 tokyo\n" pp INPUT2 # > "3 2\n" + # > "mako 13 08/08 nara\n" + # > "taisei 16 12/04 nagano\n" + # > "megumi 14 11/02 saitama\n" + # > "2 taihei\n" + # > "3 megu\n" pp OUTPUT2 # > "mako 13 08/08 nara\n" + # > "taihei 16 12/04 nagano\n" + # > "megu 14 11/02 saitama\n"
続いてStudentクラス
と問題を解くsolveメソッド
を変数の下に定義します。
まず、Studentクラス
を定義します。
- インスタンス変数
- 名前 @name : String
- 年齢 @old : Integer
- 誕生日 @birth : String
- 出身地 @state : String
- インスタンスメソッド
- info : 名前, 年齢, 誕生日, 出身地 を半角スペースで連結した文字列を返す
- changeName(new_name) :
インスタンス変数@name
をnew_name
で上書きする
class Student def initialize(name, old, birth, state) @name = name @old = old @birth = birth @state = state end def info [@name, @old, @birth, @state].join(" ") end def changeName(new_name) @name = new_name end end # 確認用コード student = Student.new("koko", 23, "04/10", "tokyo") p student.info # > "koko 23 04/10 tokyo" student.changeName("nana") p student.info # > "nana 23 04/10 tokyo"
クラスの動作が良さそうなら、確認用コードを削除してStudentクラス
の下にsolveメソッド
を記述していきます。
まず、入力データを受け取る処理を書きます。
def solve(input_lines) # 入力データ受け取り input_lines = input_lines.split("\n") n, k = input_lines.shift.split.map(&:to_i) students = input_lines.shift(n) requests = input_lines.shift(k) # 確認用コード [students, requests] end # 確認用コード pp solve(INPUT1) # > [["koko 23 04/10 tokyo"], ["1 nana"]] pp solve(INPUT2) # > [["mako 13 08/08 nara", "taisei 16 12/04 nagano", "megumi 14 11/02 saitama"], # > ["2 taihei", "3 megu"]]
データが正しく受け取れていれば、solve メソッド
に処理を追加していきます。
- 生徒のデータを受け取って
Studentクラス
でインスタンス化して配列students
に格納する。 - 名前変更の要求があれば対象インスタンスで
changeNameメソッド
を実行する。 配列students
の先頭から順にinfoメソッド
を実行し、結果の配列をresult
に格納する。
def solve(input_lines) # 入力データ受け取り input_lines = input_lines.split("\n") n, k = input_lines.shift.split.map(&:to_i) students = input_lines.shift(n) requests = input_lines.shift(k) # students をインスタンスの配列に置き換える students.map! do |student| name, old, birth, state = student.split Student.new(name, old.to_i, birth, state) end # 名前の変更要求があればchangeNameを呼び出す requests.each do |request| id, new_name = request.split students[id.to_i - 1].changeName(new_name) end # 改名処理後の生徒情報の配列を result に格納する result = students.map { |student| student.info } # 確認用コード result end # 確認用コード p solve(INPUT1) # > ["nana 23 04/10 tokyo"] p solve(INPUT1) == OUTPUT1 # > false p solve(INPUT2) # > ["mako 13 08/08 nara", "taihei 16 12/04 nagano", "megu 14 11/02 saitama"] p solve(INPUT2) == OUTPUT2 # > false
あとは出力形式を整えれば完成です。
p solve(INPUT1) == OUTPUT1 -> true
を確認するために、配列resultの要素を半角スペースで結合し末尾に改行を加えます。
def solve(input_lines) # 入力データ受け取り input_lines = input_lines.split("\n") n, k = input_lines.shift.split.map(&:to_i) students = input_lines.shift(n) requests = input_lines.shift(k) # students をインスタンスの配列に置き換える students.map! do |student| name, old, birth, state = student.split Student.new(name, old.to_i, birth, state) end # 名前の変更要求があればchangeNameを呼び出す requests.each do |request| id, new_name = request.split students[id.to_i - 1].changeName(new_name) end # 改名処理後の生徒情報の配列を result に格納する result = students.map { |student| student.info } # resultの要素を半角スペースで結合し末尾に改行を追加 result.join("\n") << "\n" end # 確認用コード p solve(INPUT1) # > "nana 23 04/10 tokyo\n" p solve(INPUT1) == OUTPUT1 # > true p solve(INPUT2) # > "mako 13 08/08 nara\ntaihei 16 12/04 nagano\nmegu 14 11/02 saitama\n" p solve(INPUT2) == OUTPUT2 # > true
入出力例での動作確認が出来ましたので、標準入力からのデータ受け取りに変更して、手動で動作確認をしたら提出します。
複数行のデータ受け取りなので STDIN.read
を使います。(入力終了は Ctrl+D
又は Ctrl+Z
)
解答コード
class Student def initialize(name, old, birth, state) @name = name @old = old @birth = birth @state = state end def info [@name, @old, @birth, @state].join(" ") end def changeName(new_name) @name = new_name end end def solve(input_lines) # 入力データ受け取り input_lines = input_lines.split("\n") n, k = input_lines.shift.split.map(&:to_i) students = input_lines.shift(n) requests = input_lines.shift(k) # students をインスタンスの配列に置き換える students.map! do |student| name, old, birth, state = student.split Student.new(name, old.to_i, birth, state) end # 名前の変更要求があればchangeNameを呼び出す requests.each do |request| id, new_name = request.split students[id.to_i - 1].changeName(new_name) end # 改名処理後の生徒情報の配列を result に格納する result = students.map { |student| student.info } # resultの要素を半角スペースで結合し末尾に改行を追加 result.join("\n") << "\n" end puts solve(STDIN.read)
今回のまとめ
今回はクラスを使って練習問題を解きました。
- クラスの定義
- インスタンス変数(ゲッター・セッター)
- initializeメソッド(コンストラクタ)
- インスタンスメソッド
慣れないうちはオブジェクト指向で問題を解こうとすると時間がかかるので、一旦手続き型で実装した後じっくり考えてみるのも良いかもしれませんね。