paiza プログラミング

[Ruby]paiza クラス・構造体メニュー 静的メンバ (paizaランク B 相当)の解説 後半: STEP4〜FINAL

static_member_2

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

今回はpaiza クラス・構造体メニュー から静的メンバという問題を解説します。

この問題集はクラスの静的メンバ(クラス変数)や継承に関する5個のSTEP問題(C~B級)FINAL問題(B級)で構成されていて、STEP問題を解いて行けばFINAL問題も解けるはず!となっています。

この問題集はボリュームがありますので前半と後半で記事を分けています。

後半(STEP4, 5, FINAL)は継承やクラス変数・クラスメソッドに関するB級相当の問題です。

STEP問題を解いてみる(後半)

簡単な解説は付けていますが、難しいと感じたら下記の記事も参考にしてみて下さい。

STEP4: クラスの継承 (paizaランク B 相当)

問題

paiza 国の大衆居酒屋で働きながらクラスの勉強をしていたあなたは、お客さんをクラスに見立てることで店内の情報を管理できることに気付きました。
全てのお客さんは、ソフトドリンクと食事を頼むことができます。
paiza 国の法律では、 20 歳以上のお客さんは成人とみなされ、お酒を頼むことができます。
20 歳未満のお客さんは未成年とみなされ、お酒を頼もうとした場合はその注文は取り消されます。
また、お酒を頼んだ場合、以降の全ての食事の注文 が毎回 200 円引きになります.

店内の全てのお客さんの数と注文の回数、各注文をしたお客さんの番号とその内容が与えられるので、各お客さんの会計を求めてください。

ヒント

注文について、20 歳未満のお客さんにできて、 20 歳以上のお客さんにできないことはないので、20歳未満のお客さんのクラスを作成して、それを継承して 20歳以上のお客さんのクラスを作成することで効率よく実装することができます。

入力される値

入力は以下のフォーマットで与えられます。

N K
a_1
...
a_N
n_1 s_1 m_1
...
n_K s_K m_K

  • 1 行目では、お客さんの人数 N と注文の回数 K が与えられます。
  • 続く N 行のうち i 行目(1 ≦ i ≦ N)では、i 番目のお客さんの年齢が与えられます。
  • 続く K 行では、頼んだお客さんの番号 n_i , 注文の種類 s_i , 値段 m_i (1 ≦ i ≦ K) が与えられます。

入力値最終行の末尾に改行が1つ入ります。

期待する出力

sum_1
...
sum_N

  • i 番目のお客さんの会計 sum_i を N 人のお客さんについて以上の形式で出力してください。

条件

すべてのテストケースにおいて、以下の条件をみたします。

  • 1 ≦ N , K ≦ 1000
  • 1 ≦ a_i ≦ 100 (1 ≦ i ≦ N)
  • 1 ≦ n_i ≦ N (1 ≦ i ≦ K)
  • 1 ≦ s_i ≦ N (1 ≦ i ≦ K) は "food" , "softdrink" , "alcohol" のいずれかです。
    food , softdrink , alcohol はその注文が食事・ソフトドリンク・お酒であることを表しています。
  • 300 ≦ m_i ≦ 5000 (1 ≦ i ≦ K)

解答例

Customerクラス

  • インスタンス変数
    • 年齢 @age : Integer
    • メニュー @menu : Array
    • 支払額 @payment: Integer(外部からの参照可)
  • initializeメソッド(引数: age)
    • @age を age で初期化する
    • @menu を ["food", "softdrink"] で初期化する
    • @payment を 0 で初期化する
  • インスタンスメソッド
    • order(引数: item, price):
      @menu  に item が含まれていれば @payment に price を加算する
      ※ 未成年でもアルコールを注文する可能性ありますが却下されます。

AdultCustomerクラス(Customerクラスを継承)

  • 定数
    • 値引き額 DISCOUNT = 200
  • インスタンス変数
    • 値引きフラグ @discount : boolean
  • initializeメソッド(引数: age)
    • super (引数: ageを渡してCustomerクラスのinitializeメソッドを呼び出す)
    • @menu に ["alcohol"] を追加する
    • @discount を false で初期化する
  • インスタンスメソッド
    • order(引数: item, price)
      • super (引数: item, priceを渡してCustomerクラスのorderメソッドを呼び出す)
      • @discountがtrueでitemがfoodなら@paymentをDISCOUNTで減算する
      • @discountがfalseでitemが"alcohol"なら@discountをtrueにする
INPUT1 = <<~"EOS"
  2 5
  59
  5
  2 food 1223
  1 alcohol 4461
  1 alcohol 4573
  1 alcohol 1438
  2 softdrink 1581
EOS
OUTPUT1 = <<~"EOS"
  10472
  2804
EOS

INPUT2 = <<~"EOS"
  7 7
  62
  91
  29
  33
  79
  15
  91
  2 food 3134
  7 alcohol 2181
  6 softdrink 4631
  3 softdrink 3120
  4 softdrink 4004
  6 alcohol 1468
  6 alcohol 1245
EOS
OUTPUT2 = <<~"EOS"
  0
  3134
  3120
  4004
  0
  4631
  2181
EOS

class Customer
  # payment の参照を許可
  attr_reader :payment

  def initialize(age)
    @age = age
    @menu = ["food", "softdrink"]
    @payment = 0
  end

  def order(item, price)
    @payment += price if @menu.include?(item)
  end
end

class AdultCustomer < Customer
  DISCOUNT = 200

  def initialize(age)
    super
    # 成人メニュー
    @menu << "alcohol"
    # 値引きフラグ
    @discount = false
  end

  def order(item, price)
    super
    if @discount
      # food 注文なら値引き
      @payment -= DISCOUNT if item == "food"
    else
      # アルコール注文で値引き true
      @discount = true if item == "alcohol"
    end
  end
end

def solve(input_data)
  # 入力データ受け取り
  input_data = input_data.split("\n")
  n, k = input_data.shift.split.map(&:to_i)
  customers = input_data.shift(n).map(&:to_i)
  requests = input_data.shift(k).map(&:split)

  # customers をインスタンス化して配列を上書きする
  customers.map! do |age|
    if age < 20
      # 未成年なら Customer クラスでインスタンス化
      Customer.new(age)
    else
      # 成人なら AdultCustomer クラスでインスタンス化
      AdultCustomer.new(age)
    end
  end

  # 注文の処理
  requests.each do |idx, item, price|
    idx, price = [idx, price].map(&:to_i)

    # 引数に item, price を与えて order を実行
    customers[idx - 1].order(item, price)
  end

  # 注文が終わったら customers の先頭から順に payment を参照して配列に格納
  result = customers.map { |customer| customer.payment }

  # 支払い料金を改行で連結し末尾に改行を追加
  result.join("\n") << "\n"
end

puts solve(STDIN.read)

# [参考 確認用コード]
# p solve(INPUT1)
# > "10472\n2804\n"
# p solve(INPUT1) == OUTPUT1
# > true
# p solve(INPUT2)
# > "0\n3134\n3120\n4004\n0\n4631\n2181\n"
# p solve(INPUT2) == OUTPUT2
# > true

solveメソッドの処理内容

  • 入力データ受け取り
    • 人数n, 注文数kを整数型で受け取ります。
    • n人分の年齢を受け取り、整数型に変換して配列customersに格納します。
    • k件の注文データ情報を受け取り、半角スペースで分割して配列requestsに格納します。
  • 配列customersの先頭から順にインスタンス化した配列で上書きします。
    • 未成年ならCustomerクラスでインスタンス化
    • 成人ならAdultCusomerクラスでインスタンス化
  • 配列requestsの先頭から順に注文の処理を行います。
    • 顧客をidxで指定して、そのインスタンスにitem, priceを与えてorderメソッドを実行します。
  • 配列customersの先頭から順に@paymentを参照した結果を配列resultに格納します。
  • 配列resultを改行で連結して末尾に改行を入れて返します。

STEP5: デフォルト引数 (paizaランク B 相当)

問題

居酒屋で働きながらクラスの勉強をしていたあなたは、お客さんをクラスに見立てることで店内の情報を管理できることに気付きました。
全てのお客さんは、ソフトドリンクと食事を頼むことができます。加えて 20 歳以上のお客さんはお酒を頼むことができます。
20 歳未満のお客さんがお酒を頼もうとした場合はその注文は取り消されます。
また、お酒(ビールを含む)を頼んだ場合、以降の全ての食事の注文 が毎回 200 円引きになります。

今回、この居酒屋でビールフェスをやることになり、ビールの注文が相次いだため、いちいちビールの値段である 500 円を書くのをやめ、伝票に注文の種類と値段を書く代わりに 0 とだけを書くことになりました。

店内の全てのお客さんの数と注文の回数、各注文をしたお客さんの番号とその内容が与えられるので、各お客さんの会計を求めてください。

入力される値

入力は以下のフォーマットで与えられます。

N K
a_1
...
a_N
n_1 o_1
...
n_K o_K

  • 1 行目では、お客さんの人数 N と注文の回数 K が与えられます。
  • 続く N 行のうち i 行目(1 ≦ i ≦ N)では、i 番目のお客さんの年齢が与えられます。
  • 続く K 行では、頼んだお客さんの番号 n_i , 注文を表す文字列 o_i が与えられます。
  • o_i では、注文の種類 s_i と 値段 m_i (1 ≦ i ≦ K) を表す文字列 "s_i m_i" または、ビールの注文を表す "0" が与えられます。

入力値最終行の末尾に改行が1つ入ります。

期待する出力

sum_1
...
sum_N

  • i 番目のお客さんの会計 sum_i を N 人のお客さんについて以上の形式で出力してください。

条件

すべてのテストケースにおいて、以下の条件をみたします。

  • 1 ≦ N , K ≦ 1000
  • 1 ≦ a_i ≦ 100 (1 ≦ i ≦ N)
  • 1 ≦ n_i ≦ N (1 ≦ i ≦ K)

o_i (1 ≦ i ≦ K) は次のうちのいずれかの形式です。

  • "s_i m_i"

1 ≦ s_i ≦ N (1 ≦ i ≦ K) は "food" , "softdrink" , "alcohol" のいずれかです。
food , softdrink , alcohol はその注文が食事・ソフトドリンク・お酒であることを表しています。
また、300 ≦ m_i ≦ 5000 です。

  • "0"

その注文がビールであることを表す。

解答例

Customerクラス

  • インスタンス変数
    • 年齢 @age : Integer
    • メニュー @menu : Array
    • 支払額 @payment: Integer(外部からの参照可)
  • initializeメソッド(引数: age)
    • @age を age で初期化する
    • @menu を ["food", "softdrink"] で初期化する
    • @payment を 0 で初期化する
  • インスタンスメソッド
    • order(引数: item = "alcohol", price = 500):
      @menu  に item が含まれていれば @payment に price を加算する
      ※ 未成年でもアルコールを注文する可能性ありますが却下されます。

AdultCustomerクラス(Customerクラスを継承)

  • 定数
    • 値引き額 DISCOUNT = 200
  • インスタンス変数
    • 値引きフラグ @discount : boolean
  • initializeメソッド(引数: age)
    • super (引数: ageを渡してCustomerクラスのinitializeメソッドを呼び出す)
    • @menu に ["alcohol"] を追加する
    • @discount を false で初期化する
  • インスタンスメソッド
    • order(引数: item = "alcohol", price = 500)
      • super (引数: item, priceを渡してCustomerクラスのorderメソッドを呼び出す)
      • @discountがtrueでitemがfoodなら@paymentをDISCOUNTで減算する
      • @discountがfalseでitemが"alcohol"なら@discountをtrueにする
INPUT1 = <<~"EOS"
  3 5
  19
  43
  22
  2 0
  2 food 4333
  1 0
  2 0
  1 food 4606
EOS
OUTPUT1 = <<~"EOS"
  4606
  5133
  0
EOS

INPUT2 = <<~"EOS"
  5 10
  1
  13
  31
  74
  34
  1 food 1088
  4 alcohol 3210
  1 alcohol 599
  2 alcohol 602
  2 softdrink 4375
  4 food 1752
  2 0
  5 alcohol 4565
  3 0
  2 0
EOS
OUTPUT2 = <<~"EOS"
  1088
  4375
  500
  4762
  4565
EOS

class Customer
  attr_reader :payment

  def initialize(age)
    @age = age
    @menu_item = ["food", "softdrink"]
    @payment = 0
  end

  def order(item = "alcohol", price = 500)
    @payment += price if @menu_item.include?(item)
  end
end

class AdultCustomer < Customer
  DISCOUNT = 200

  def initialize(age)
    super
    @menu_item << "alcohol"
    @discount = false
  end

  def order(item = "alcohol", price = 500)
    super
    if @discount
      # food 注文なら値引き
      @payment -= DISCOUNT if item == "food"
    else
      # アルコール注文で値引き true
      @discount = true if item == "alcohol"
    end
  end
end

def solve(input_data)
  # 入力データ受け取り
  input_data = input_data.split("\n")
  n, k = input_data.shift.split.map(&:to_i)
  customers = input_data.shift(n).map(&:to_i)
  requests = input_data.map(&:split)

  # customers をインスタンス化して配列を上書きする
  customers.map! do |age|
    if age < 20
      # 未成年なら Customer クラスでインスタンス化
      Customer.new(age)
    else
      # 成人なら AdultCustomer クラスでインスタンス化
      AdultCustomer.new(age)
    end
  end

  # 注文の処理
  requests.each do |number, item, price|
    number, price = [number, price].map(&:to_i)

    if item == "0"
      # "0" なら引数無しで order を実行
      customers[number - 1].order
    else
      # 引数に item, price を与えて order を実行
      customers[number - 1].order(item, price.to_i)
    end
  end

  # 注文が終わったら customers の先頭から順に payment を参照して配列に格納
  result = customers.map { |customer| customer.payment }

  # 支払い料金を改行で連結し末尾に改行を追加
  result.join("\n") << "\n"
end

puts solve(STDIN.read)

# p solve(INPUT1)
# > "4606\n5133\n0\n"
# p solve(INPUT1) == OUTPUT1
# > true
# p solve(INPUT2)
# > "1088\n4375\n500\n4762\n4565\n"
# p solve(INPUT2) == OUTPUT2
# > true

 

solveメソッドの処理内容

  • 入力データ受け取り
    • 人数n, 注文数kを整数型で受け取ります。
    • n人分の年齢を受け取り、整数型に変換して配列customersに格納します。
    • k件の注文データ情報を受け取り、半角スペースで分割して配列requestsに格納します。
  • 配列customersの先頭から順にインスタンス化した配列で上書きします。
    • 未成年ならCustomerクラスでインスタンス化
    • 成人ならAdultCusomerクラスでインスタンス化
  • 配列requestsの先頭から順に注文の処理を行います。
    • idxで対象顧客(インスタンス) を選択します。
      • item”0”なら引数なしでorderメソッドを実行します。
        (ビールを注文します。)
      • item, priceを与えてorderメソッドを実行します。
  • 配列customersの先頭から順に@paymentを参照した結果を配列resultに格納します。
  • 配列resultを改行で連結して末尾に改行を入れて返します。

静的メンバ (paizaランク B 相当)を解いてみる

※ paizaレベルアップ問題集 クラス・構造体メニューより

問題

居酒屋で働きながらクラスの勉強をしていたあなたは、お客さんをクラスに見立てることで勤務時間中の店内の人数や注文の情報を管理できることに気付きました。
全てのお客さんは、ソフトドリンクと食事を頼むことができます。加えて 20 歳以上のお客さんはお酒を頼むことができます。
20 歳未満のお客さんがお酒を頼もうとした場合はその注文は取り消されます。
また、お酒(ビールを含む)を頼んだ場合、以降の全ての食事の注文 が毎回 200 円引きになります。

今回、この居酒屋でビールフェスをやることになり、ビールの注文が相次いだため、いちいちビールの値段である 500 円を書くのをやめ、注文の種類と値段を書く代わりに 0 とだけを書くことになりました。

勤務時間の初めに店内にいるお客さんの人数と与えられる入力の回数、各注文をしたお客さんの番号とその内容、または退店したお客さんの番号が与えられます。
お客さんが退店する場合はそのお客さんの会計を出力してください。勤務時間中に退店した全てのお客さんの会計を出力したのち、勤務時間中に退店した客の人数を出力してください。

入力される値

入力は以下のフォーマットで与えられます。

N K
a_1
...
a_N
n_1 o_1
...
n_K o_K

  • 1 行目では、お客さんの人数 N と入力の回数 K が与えられます。
  • 続く N 行のうち i 行目(1 ≦ i ≦ N)では、i 番目のお客さんの年齢が与えられます。
  • 続く K 行では、頼んだお客さんの番号 n_i , 注文を表す文字列 o_i が与えられます。
  •  o_i では、注文の種類 s_i と 値段 m_i (1 ≦ i ≦ K) を表す文字列 "s_i m_i" または、ビールの注文を表す "0" または、そのお客さんが会計を行い帰ることを表す "A" が与えられます。

入力値最終行の末尾に改行が1つ入ります。

期待する出力

sum_1
...
C

お客さんが帰るたびにそのお客さんの会計を出力してください。 1 人の会計ごとに改行を行ってください。
勤務時間中に帰った全てのお客さんの会計を出力したのち、勤務時間中に退店した客の人数 C を出力してください。

条件

すべてのテストケースにおいて、以下の条件をみたします。

  • 1 ≦ N , K ≦ 1000
  • 1 ≦ a_i ≦ 100 (1 ≦ i ≦ N)
  • 1 ≦ n_i ≦ N (1 ≦ i ≦ K)

o_i (1 ≦ i ≦ K) は次のうちのいずれかの形式です。

  • "s_i m_i"

1 ≦ s_i ≦ N (1 ≦ i ≦ K) は "food" , "softdrink" , "alcohol" のいずれかです。
food , softdrink , alcohol はその注文が食事・ソフトドリンク・お酒であることを表しています。また、300 ≦ m_i ≦ 5000 です。

  • "0"

その注文がビールであることを表す。

  •  "A"

n_i 番のお客さんが会計をして退店することを表す。

入力例1
2 3
20
30
1 0
2 0
1 A
出力例1
500
1
入力例2
7 12
68
85
57
32
90
74
7
2 0
4 A
3 0
1 A
4 softdrink 3781
6 softdrink 3010
4 0
5 alcohol 1018
1 0
1 softdrink 376
1 softdrink 797
2 alcohol 4284
出力例2
0
0
2
攻略ポイント

ポイント

  • 継承で未成年クラスと成人クラスを実装する
  • クラス変数で来店者数(精算した顧客数)を管理する
  • クラス変数を参照するクラスメソッドを実装する
問題を解く流れ

入出力例をコピペしてヒアドキュメントで変数に代入し、データを確認します。正しく受け取れていれば確認用コードは削除します。(データが多い時はppメソッドを使うといい感じに出力してくれます)

INPUT1 = <<~"EOS"
  2 3
  20
  30
  1 0
  2 0
  1 A
EOS
OUTPUT1 = <<~"EOS"
  500
  1
EOS

INPUT2 = <<~"EOS"
  7 12
  68
  85
  57
  32
  90
  74
  7
  2 0
  4 A
  3 0
  1 A
  4 softdrink 3781
  6 softdrink 3010
  4 0
  5 alcohol 1018
  1 0
  1 softdrink 376
  1 softdrink 797
  2 alcohol 4284
EOS
OUTPUT2 = <<~"EOS"
  0
  0
  2
EOS

# 確認用コード
pp INPUT1
# > "2 3\n" + "20\n" + "30\n" + "1 0\n" + "2 0\n" + "1 A\n"
p OUTPUT1
# > "500\n1\n"
pp INPUT2
# > "7 12\n" +
# > "68\n" +
# > "85\n" +
# > "57\n" +
# > "32\n" +
# > "90\n" +
# > "74\n" +
# > "7\n" +
# > "2 0\n" +
# > "4 A\n" +
# > "3 0\n" +
# > "1 A\n" +
# > "4 softdrink 3781\n" +
# > "6 softdrink 3010\n" +
# > "4 0\n" +
# > "5 alcohol 1018\n" +
# > "1 0\n" +
# > "1 softdrink 376\n" +
# > "1 softdrink 797\n" +
# > "2 alcohol 4284\n"
p OUTPUT2
# > "0\n0\n2\n"

続いてCustomerクラス, AdultCustomerクラスと問題を解くsolveメソッドを変数の下に定義します。

まず、クラスを定義します。

Customerクラス

  • クラス変数
    • 来店者数 @count : Integer
  • クラスメソッド
    • Customer.visitor_count : @@count を返す
      (self.visitor_countと同じ)
  • インスタンス変数
    • 年齢 @age : Integer
    • メニュー @menu : Array
    • 支払額 @payment: Integer(外部からの参照可)
  • initializeメソッド(引数: age)
    • @age を age で初期化する
    • @menu を ["food", "softdrink"] で初期化する
    • @payment を 0 で初期化する
  • インスタンスメソッド
    • order(引数: item = "alcohol", price = 500):
      @menu  に item が含まれていれば @payment に price を加算する
      ※ 未成年でもアルコールを注文する可能性ありますが却下されます。
    • check_out :
      @@countをインクリメントする
      @paymentを返す

AdultCustomerクラス(Customerクラスを継承)

  • 定数
    • 値引き額 DISCOUNT = 200
  • インスタンス変数
    • 値引きフラグ @discount : boolean
  • initializeメソッド(引数: age)
    • super (引数: ageを渡してCustomerクラスのinitializeメソッドを呼び出す)
    • @menu に ["alcohol"] を追加する
    • @discount を false で初期化する
  • インスタンスメソッド
    • order(引数: item = "alcohol", price = 500)
      • super (引数: item, priceを渡してCustomerクラスのorderメソッドを呼び出す)
      • @discountがtrueでitemがfoodなら@paymentをDISCOUNTで減算する
      • @discountがfalseでitemが"alcohol"なら@discountをtrueにする
class Customer
  # 来店者数(清算時にカウントアップ)
  @@count = 0

  # 来店者数を返すクラスメソッド
  def Customer.visitor_count
    @@count
  end

  def initialize(age)
    @age = age
    @menu_item = ["food", "softdrink"]
    @payment = 0
  end

  def order(item = "alcohol", price = 500)
    @payment += price if @menu_item.include?(item)
  end

  def checkout
    @@count += 1
    @payment
  end
end

class AdultCustomer < Customer
  DISCOUNT = 200

  def initialize(age)
    super
    @menu_item << "alcohol"
    @discount = false
  end

  def order(item = "alcohol", price = 500)
    super
    if @discount
      # food 注文なら値引き
      @payment -= DISCOUNT if item == "food"
    else
      # アルコール注文で値引き true
      @discount = true if item == "alcohol"
    end
  end
end

# 確認用コード
# Customer確認
kodomo = Customer.new(10)
kodomo.order("food", 1000)
kodomo.order("softdrink", 500)
kodomo.order("alcohol", 800)
kodomo.order
pp kodomo
# > #<Customer:0x00007fafab10e840
# >  @age=10,
# >  @menu_item=["food", "softdrink"],
# >  @payment=1500>
p kodomo.checkout
# > 1500

# AdultCustomer確認
otona = AdultCustomer.new(20)
otona.order("food", 1000)
otona.order("softdrink", 500)
otona.order("alcohol", 800)
otona.order
pp otona
# > #<AdultCustomer:0x00007fafaf01ccd0
# > @age = 20,
# > @discount = true,
# > @menu_item = ["food", "softdrink", "alcohol"],
# > @payment = 2800 >
otona.order("food", 1000)
p otona.checkout
# > 3600

# visitor_count確認
p Customer.visitor_count
# > 2

クラスの動作が良さそうなら、確認用コードを削除してAdultCustomerクラスの下にsolveメソッドを記述していきます。

まず、入力データを受け取る処理を書きます。

def solve(input_data)
  # 入力データ受け取り
  input_data = input_data.split("\n")
  n, k = input_data.shift.split.map(&:to_i)
  customers = input_data.shift(n).map(&:to_i)
  requests = input_data.map(&:split)

  # 確認用コード
  [customers, requests]
end

# 確認用コード
pp solve(INPUT1)
# > [[20, 30], [["1", "0"], ["2", "0"], ["1", "A"]]]
pp solve(INPUT2)
# > [[68, 85, 57, 32, 90, 74, 7],
# >  [["2", "0"],
# >   ["4", "A"],
# >   ["3", "0"],
# >   ["1", "A"],
# >   ["4", "softdrink", "3781"],
# >   ["6", "softdrink", "3010"],
# >   ["4", "0"],
# >   ["5", "alcohol", "1018"],
# >   ["1", "0"],
# >   ["1", "softdrink", "376"],
# >   ["1", "softdrink", "797"],
# >   ["2", "alcohol", "4284"]]]

データが正しく受け取れていれば、solve メソッドに処理を追加していきます。

  • 配列customersの先頭から順にインスタンス化した配列で上書きします。
    • 未成年ならCustomerクラスでインスタンス化
    • 成人ならAdultCusomerクラスでインスタンス化
  • 空の配列resultを用意してから、配列requestsの先頭から順に注文の処理を行います。
    • idxで対象顧客(インスタンス) を選択します。
      • item”0”なら引数なしでorderメソッドを実行します。
        (ビールを注文します。)
      • item”A”ならcheckoutメソッドを実行して戻り値を配列resultpushします。
      • それ以外ならitem, priceを与えてorderメソッドを実行します。
  • クラスメソッドCustomer.visitor_countを実行した結果を配列resultpushします。
  • 配列resultを返します。
def solve(input_data)
  # 入力データ受け取り
  input_data = input_data.split("\n")
  n, k = input_data.shift.split.map(&:to_i)
  customers = input_data.shift(n).map(&:to_i)
  requests = input_data.map(&:split)

  # customers をインスタンス化して配列を上書きする
  customers.map! do |age|
    if age < 20
      # 未成年なら Customer クラスでインスタンス化
      Customer.new(age)
    else
      # 成人なら AdultCustomer クラスでインスタンス化
      AdultCustomer.new(age)
    end
  end

  # 注文の処理
  result = []
  requests.each do |number, item, price|
    number, price = [number, price].map(&:to_i)

    case item
    when "0"
      # "0" なら引数無しで order を実行
      customers[number - 1].order
    when "A"
      # "A" なら checkout を実行
      result << customers[number - 1].checkout
    else
      # 引数に item, price を与えて order を実行
      customers[number - 1].order(item, price)
    end
  end
  # result に退店した客の人数を push
  result << Customer.visitor_count

  # 確認用コード
  result
end

# 確認用コード
# 注:@@countが連番なので個別に確認
p solve(INPUT1)
# > [500, 1]
p solve(INPUT1) == OUTPUT1
# > false
p solve(INPUT2)
# > [0, 0, 2]
p solve(INPUT2) == OUTPUT2
# > false

あとは出力形式を整えれば完成です。

p solve(INPUT1) == OUTPUT1 -> true を確認するために、配列resultの要素を半角スペースで結合し末尾に改行を加えます。

def solve(input_data)
  # 入力データ受け取り
  input_data = input_data.split("\n")
  n, k = input_data.shift.split.map(&:to_i)
  customers = input_data.shift(n).map(&:to_i)
  requests = input_data.map(&:split)

  # customers をインスタンス化して配列を上書きする
  customers.map! do |age|
    if age < 20
      # 未成年なら Customer クラスでインスタンス化
      Customer.new(age)
    else
      # 成人なら AdultCustomer クラスでインスタンス化
      AdultCustomer.new(age)
    end
  end

  # 注文の処理
  result = []
  requests.each do |number, item, price|
    number, price = [number, price].map(&:to_i)

    case item
    when "0"
      # "0" なら引数無しで order を実行
      customers[number - 1].order
    when "A"
      # "A" なら checkout を実行
      result << customers[number - 1].checkout
    else
      # 引数に item, price を与えて order を実行
      customers[number - 1].order(item, price)
    end
  end
  # result に退店した客の人数を push
  result << Customer.visitor_count

  # result を改行で連結して末尾に改行を追加
  result.join("\n") << "\n"
end

# 確認用コード
# 注:@@countが連番なので個別に確認
# p solve(INPUT1)
# > "500\n1\n"
# p solve(INPUT1) == OUTPUT1
# > true
# p solve(INPUT2)
# > "0\n0\n2\n"
# p solve(INPUT2) == OUTPUT2
# > true

入出力例での動作確認が出来ましたので、標準入力からのデータ受け取りに変更して、手動で動作確認をしたら提出します。
複数行のデータ受け取りなので STDIN.read を使います。(入力終了は Ctrl+D 又は Ctrl+Z)

解答コード
class Customer
  # 来店者数(清算時にカウントアップ)
  @@count = 0

  # 来店者数を返すクラスメソッド
  def Customer.visitor_count
    @@count
  end

  def initialize(age)
    @age = age
    @menu_item = ["food", "softdrink"]
    @payment = 0
  end

  def order(item = "alcohol", price = 500)
    @payment += price if @menu_item.include?(item)
  end

  def checkout
    @@count += 1
    @payment
  end
end

class AdultCustomer < Customer
  DISCOUNT = 200

  def initialize(age)
    super
    @menu_item << "alcohol"
    @discount = false
  end

  def order(item = "alcohol", price = 500)
    super
    if @discount
      # food 注文なら値引き
      @payment -= DISCOUNT if item == "food"
    else
      # アルコール注文で値引き true
      @discount = true if item == "alcohol"
    end
  end
end

def solve(input_data)
  # 入力データ受け取り
  input_data = input_data.split("\n")
  n, k = input_data.shift.split.map(&:to_i)
  customers = input_data.shift(n).map(&:to_i)
  requests = input_data.map(&:split)

  # customers をインスタンス化して配列を上書きする
  customers.map! do |age|
    if age < 20
      # 未成年なら Customer クラスでインスタンス化
      Customer.new(age)
    else
      # 成人なら AdultCustomer クラスでインスタンス化
      AdultCustomer.new(age)
    end
  end

  # 注文の処理
  result = []
  requests.each do |number, item, price|
    number, price = [number, price].map(&:to_i)

    case item
    when "0"
      # "0" なら引数無しで order を実行
      customers[number - 1].order
    when "A"
      # "A" なら checkout を実行
      result << customers[number - 1].checkout
    else
      # 引数に item, price を与えて order を実行
      customers[number - 1].order(item, price)
    end
  end
  # result に退店した客の人数を push
  result << Customer.visitor_count

  # result を改行で連結して末尾に改行を追加
  result.join("\n") << "\n"
end

puts solve(STDIN.read)

今回のまとめ

後半ではオブジェクト指向での大事な概念である継承を扱いました。

  • クラスの継承
  • クラス変数の定義
  • 特異メソッド(クラスメソッド)の定義と呼び出し

クラスの継承を使えば変更箇所を少なく抑えながら、色々な変更が出来るので便利ですね!



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


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






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







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







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







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


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

© 2024 じゃいごテック