paiza プログラミング

[Ruby]paiza クラス・構造体メニュー 構造体の更新 (paizaランク C 相当)の解説

クラス構造体メニュー_構造体の更新

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

今回は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) : インスタンス変数@namenew_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メソッド(コンストラクタ)
  • インスタンスメソッド

慣れないうちはオブジェクト指向で問題を解こうとすると時間がかかるので、一旦手続き型で実装した後じっくり考えてみるのも良いかもしれませんね。



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


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






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







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







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







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


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

© 2024 じゃいごテック