Open43

Paiza勉強中のメモ

なかじなかじ

配列をputsメソッドで出力する

  • 勝手に改行してくれるので便利

問題

「paiza hello」のように、半角の空白がある文字列を
paiza
hello
のように改行ありで出力する。

解答

  • 多重代入を使用
  • あらかじめ要素数が決まっている場合はこちらでOK
s, t = gets.split(' ')

puts s #=>paiza
puts t #=>hello
  • 配列の出力
s = gets.split(' ')
puts s
#=>paiza
#=>hello
p s =>["paiza", "hello"]
  • 配列をputs メソッドで出力しようとすると...

配列や文字列以外のオブジェクトが引数として与えられた場合には、当該オブジェクトを最初に to_ary により配列へ、次に to_s メソッドにより文字列へ変換を試みます。

Integerで配列にしたい場合

#入力値
200 300
#方法
x = gets.split(' ').map(&:to_i)
p x #=>[200,300]

多重代入を使う

#入力
3 5
n,m = gets.split(" ").map(&:to_i)
n#=>3
m#=>5
'one two three four five'.split(' ').each { |val| puts val }

&:について

  • 使用できるケースは基本的に下記3つのケースが揃った場合のみである(チェリー本p114)
    • ブロックパラメータが1個だけである
    • ブロック内で呼び出すメソッドに引数がない
    • ブロック内では、ブロックパラメータに対してメソッドを1回呼び出す以外の処理がない
  • すなわち下記のようなケースではこの記法はできない
    • 演算子を使用するケース
    • ブロック内のメソッドで引数を渡すケース
    • ブロック内で複数の文を実行するケース
  • 【Ruby】array.map(&:method)を理解する #Ruby - Qiita

&:を使用せず書く場合

x = gets.split(' ').map{|n| n.to_i}
p x [200,300]
なかじなかじ

チェリー本の参考になりそうな箇所🍒

繰り返し処理

  • pp153-158
なかじなかじ

配列にして各要素に対して足し算する場合

  • 入力
5 6

結果

[10,12]

mapメソッド使用

ary = gets.split(" ")
result = ary.map do |n|
  n = n.to_i
  n += n
end
p result
  • ブロック処理の注意点
ary = gets.split(" ")
result = ary.map do |n|
  n.to_i
  n += n
end
p result
#=>["55", "66"]
  • n.to_iしてもどこかに代入しない限り、n += nは文字列の連結になってしまうよ。
なかじなかじ

配列から最小値・最大値を探す

最小値

最大値

宇宙演算子

  • <=>を使った書き方もできます

問題

#入力(5つの入力限定です)
2
6
8
91
4
#出力
2
  • 自分の解答
ary = []
result = 5.times do
    ary << gets.to_i
end
p ary.min
  • 解答例
n = []
5.times { n.push gets.to_i }

puts n.min
なかじなかじ

文字列の比較

  • 文字列を2つ入力して一致していれば"OK"、異なっていれば"NGを表示

eql?メソッドを使用

x = gets.chomp
y = gets.chomp
if x.eql?(y)
    puts "OK"
else
    puts "NG"
end

==と三項演算子を使う(解答例)

a = gets.chomp
b = gets.chomp

puts a == b ? 'OK' : 'NG'

数値だと==よりeql?の方が厳密

puts 1 == 1.0 #=>true
puts 1.eql?(1.0) #=>false
なかじなかじ

指定値までの繰り返し

改行あり

  • 1から100までを改行ありで出力しましょう

timesを使う方法

100.times do |i|
    p i + 1
end

upto使う方法

 1.upto(100) { |i|
   puts i
 }
  • 先頭にputsつけると最後に1が出力されるよ
 puts 1.upto(5) { |i|
   puts i
 }
#=>1
#=>2
#=>3
#=>4
#=>5
#=>1

配列作成してputsで出力する方法

to_aメソッド(引数は()です。注意。)

n = gets.to_i
puts (1..n).to_a

アスタリスク使う方法

n = gets.to_i
puts [*1..n]
  • カッコ省略しないと下記の通り
n = gets.to_i
puts [*(1..n)]

-これでも大丈夫

n = gets.to_i
puts *(1..n)

引数の直前に * がついている場合、その引数の値が展開されて渡されます。展開はメソッド to_a を経由して行なわれます。

引用元:メソッド呼び出し(super・ブロック付き・yield) (Ruby 3.3 リファレンスマニュアル)

横並び

末尾半角スペースあり

1.upto(10){|n| print n," "}

末尾改行スペースなし

9.times do |i|
    print i + 1, " "
end
print 10
1.upto(999){
    |i| print i," "
}
print 1000
  • もっといい書き方ありそうだけど...
なかじなかじ

複数行の読み込み

IOクラスのメソッドたち

全部読み込む

1行読み込み

読み込んで一行改行スペースありで表示する例

#入力
ok
soso
bad
iine
# 出力
ok soso bad iine
n = readlines.map(&:chomp).join(" ")
puts n

繰り返し処理(回数わかっているとき)

#入力
paiza
kirishima
kyoko
#出力
paiza|kirishima|kyoko
arry = []
3.times do |s|
    s = gets.chomp
    arry << s
end
puts arry.join("|")

繰り返し処理で行う場合

回数がわかっているとき

arry = Array.new(1000)
1000.times{|i|arry[i] = gets.to_i}
arry.each{|x|puts x}
  • 配列の要素の番号に代入していくスタイル。他の例でも使えそう。
なかじなかじ

一列の文字列を区切りありで出力

問題

#入力
123456789
123,456,789
  • 問題は必ず3の倍数だった

each_with_indexを使う方法(3の倍数の時のみ)

arry = gets.split(/\s*/).map(&:to_i) #=>\s*は空白文字0文字以上の正規表現。これで一文字ずつの配列を作れる。
arry.each_with_index do |n,idx|
    if idx == 0
        result = print n.to_s
    elsif idx % 3 == 0
       result = print "," + n.to_s
    else
       result = print n.to_s
    end
    print result
end
  • idx % 3 == 0とelseの条件分岐で書いていたのだが、0がtrueになってしまうらしく冒頭にカンマが入ってしまった
  • わざわざ数字に変換する必要なかった(文字列に戻しているので)
  • Rubyアソシエーション: 正規表現

正規表現使う方法(3の倍数以外の時も可)

n = gets.chomp.reverse.scan(/\d{1,3}/).join(",").reverse
puts n
  • \d{1,3}にしないと3で割り切れない時に値が消えます
なかじなかじ

九九の表作る

書いたコード

(1..9).each do |n|
    puts [n*1,n*2,n*3,n*4,n*5,n*6,n*7,n*8,n*9].join(" ")
end
  • クソコードだなと思いつつ、ブロック内での処理が書けず断念

GPT先生に聞く

(1..9).each { |n| puts (1..9).map { |i| n * i }.join(" ") }
  • 知っているメソッドしかないけど書けない。

九九の表(罫線あり)

column = []
(1..9).each do |n|
    row = []
    (1..9).each do |m|
        row << sprintf("%2d" % (n * m))
    end
    column << row.join(" | ") + "\n"
end
table = column.join("=" * 42 + "\n")
puts table
  • pushメソッドより<<の方が個人的によく使うので書き換えてます
なかじなかじ

すべての行の長さが不定な 2 次元配列の出力

問題

#入力
4
1 3 2 4
#出力
1
1 2 3
1 2
1 2 3 4

自分の回答

n = gets.to_i
arry = gets.split(" ").map(&:to_i)
arry.each do 
    |i| x = 1.upto(i) #=>ここで出力するとEnumerableになってしまう
    puts x.to_a.join(" ")
end
  • ちょっとすっきり書く方法
n = gets.to_i
arry = gets.split.map(&:to_i)
arry.each { |i| puts (1..i).to_a.join(" ") }

似たような難しい問題

問題

#入力
10 4 #=>10が要素数、4が分割の個数
1 2 3 4 5 6 7 8 9 10#=>分割する配列
2 6 1 1#=>分ける個数
#出力
1 2
3 4 5 6 7 8
9
10

回答

n,m = gets.split(' ').map(&:to_i) #=>ここの値使ってないなぁ
arry1 = gets.split(' ').map(&:to_i)
arry2 = gets.split(' ').map(&:to_i)
s = 0
arry2.each{|num|
    puts arry1[s,num].join(" ")
    s += num
}
  • ChatGPTにも聞いたけど同じ回答だった
  • ブロック処理だとスコープあまり意識しなくて良いので嬉しい
なかじなかじ

配列内に要素があるかわかるメソッド

三項演算子と組み合わせすっきり書ける

  • 食べたいものがmenuの中にあるか
  • 最初配列を回して一つずつ確認しようとしてたけどこっちの方が便利
  • ただ完全一致しか調べられないので一部一致とかの時は配列回して確認する必要がありそう
menus.include?(want_to_eat)? "Yes" : "No"

文字列でもinclude?使えます

なかじなかじ

整数か小数かどちらか入力されてそれをIntegerかfloatで出力したい

  • 入力値を小数で受け取ってそれを整数値に変換
  • ==で同値であるか確認し、同値であれば整数で、異なれば小数で出力
  • ちなみに、eql?にしてしまうと1.eql?(1.0)はfalse(クラスも含めて判定するため)になってしまいます
n = gets.to_f
puts n == n.to_i ? n.to_i : n

小数点切り捨て

なかじなかじ

フォーマット文字列

問題

  • 整数の場合は整数、小数の場合は指定された桁数で入力値で桁が足りない場合は0で埋める
#入力①
0.813 2
#出力①
0.81
#入力②
0.813 4
#出力②
0.8130

回答

n, m = gets.split(" ")
num = n.to_f
digits = m.to_i
format_num = sprintf("%.#{digits}f", num)
puts format_num

問題

1行目に要素数、2行目以下は上記と同じような感じ

4
0.813 1
0.813 2
0.813 3
0.813 4
0.8
0.81
0.813
0.8130

回答

x = gets.to_i
x.times do 
    n,m = gets.split(" ")
    num = n.to_f
    digits = m.to_i
    rounded_num = num.round(digits) #この行ないと1ケース失敗した
    format_num = sprintf("%.#{digits}f", rounded_num)
    puts format_num
end

数字3桁分で出力

n = gets.to_i
puts sprintf("%3d" % n)
n = gets.to_i
# 出力
puts("%3d" % n)

式展開が必要な例

#入力
813 3
813 4
#出力
813
813 #4桁分で出力
n,m = gets.split(" ").map(&:to_i)
puts sprintf("%#{m}d", n)

数字3桁分で前の箇所を0で埋める

n = gets.to_i
puts sprintf("%03d", n)
なかじなかじ

ペアの数値の入った表を罫線入りで出力

問題

#入力
2 3 7 8
#出力
(7, 8) | (7, 8) | (7, 8)
========================
(7, 8) | (7, 8) | (7, 8)

回答

n,m,x,y = gets.chomp.split(" ").map(&:to_i)
row = Array.new(m, "(#{x}, #{y})")
column = []
n.times do
  column << row.join(" | ") + "\n"
end
l = row.join(" | ") .length
table = column.join("=" * l  + "\n")
puts table

各9桁のフォーマット文字列のバージョン

n,m,x,y = gets.chomp.split(" ").map(&:to_i)
a = sprintf("%9d",x)
b = sprintf("%9d",y)
row = Array.new(m, "(#{a}, #{b})")
column = []
n.times do
  column << row.join(" | ") + "\n"
end
l = row.join(" | ") .length
table = column.join("=" * l  + "\n")
puts table
なかじなかじ

入力値で不要な要素がある場合

#入力
5 1 2 3 4 5
#出力
1
2
3
4
5
  • 頭の5は要素数で、出力する際使用しない値

可変長引数使う

n, *m = gets.split(" ").map(&:to_i)
puts m #m.each{|val| puts val}

shiftを使う

a = gets.split(" ").map(&:to_i)
a.shift
puts a #a.each{|val| puts val}
なかじなかじ

Array.newでインデックスを使って

n = gets.to_i

a = Array.new(n)
b = Array.new(n)
n.times { |i| a[i], b[i] = gets.split(' ').map(&:to_i) }
  • 多重代入を使うとスッキリ書けるなぁ
なかじなかじ

二次元配列を使った回答

問題

#入力
2
1 2 3
8 1 3
#出力
1 2 3
8 1 3
  • 各段の数字の個数は3で固定

回答

n = gets.to_i
arry = Array.new(n).map { Array.new(3) }
p arry #=>[[nil,nil,nil],[nil,nil,nil]]
n.times do |i|
    row = gets.split(" ").map(&:to_i)
    3.times{|j| arry[i][j] = row[j]}
end
arry.each { |row| puts row.join(' ') }
  • arry[0][0]なら[[★,nil,nil],[nil,nil,nil]]で★の箇所に代入される
なかじなかじ

3*3で配列から要素を取り出す

# すべての3x3ブロックを取り出す
arr = [[1,2,3,4,5],[1,2,3,4,5],[1,2,3,4,5],[1,2,3,4,5],[1,2,3,4,5],]
(0..2).each do |i|  # 行の開始位置(0から2まで)
  (0..2).each do |j|  # 列の開始位置(0から2まで)
    # 3x3のブロックを取得
    sub_array = arr[i, 3].map { |row| row[j, 3] }
    p sub_array  
  end
end
#=>[[1, 2, 3], [1, 2, 3], [1, 2, 3]]
#=>[[2, 3, 4], [2, 3, 4], [2, 3, 4]]
#=>[[3, 4, 5], [3, 4, 5], [3, 4, 5]]
#=>[[1, 2, 3], [1, 2, 3], [1, 2, 3]]
#...
なかじなかじ

クラス・構造体メニュー①(表示)

問題

#入力例
1
koko 23 04/10 tokyo
#出力例
User{
nickname : koko
old : 23
birth : 04/10
state : tokyo
}

回答

n = gets.to_i
class User
  attr_accessor :nickname, :old, :birth, :state

  def initialize(nickname, old, birth, state)
    @nickname = nickname
    @old = old
    @birth = birth
    @state = state
  end

  def to_s
    "User{\nnickname : #{@nickname}\nold : #{@old}\nbirth : #{@birth}\nstate : #{@state}\n}"
  end
end

def parse_input(input)
  # 入力を分割して各部分を取得
  parts = input.split(" ")
  nickname = parts[0]
  old = parts[1].to_i
  birth = parts[2]
  state = parts[3]

  # Userオブジェクトを作成して返す
  User.new(nickname, old, birth, state)
end
n.times do
  input = gets
  user = parse_input(input)
  puts user
end
  • 無理やり配列でなんでも解かずにこうやってオブジェクト指向言語の良さを活かして問題解けるようになりたいぜ
なかじなかじ

クラス・構造体メニュー①(検索)

問題

#入力
3 #人数
mako 13 08/08 nara #名前・年齢・生年月日・出身地
megumi 14 11/02 saitama
taisei 16 12/04 nagano
14 #=>この年齢の人を出力
#出力
megumi
  • クラス使って解いてみる

回答

n = gets.to_i
class User
    attr_accessor :name, :old, :birth, :state
    
    def initialize(name, old, birth, state)
        @name = name
        @old = old
        @birth = birth
        @state = state
    end
    
end

def parse_input(input)
    parts = input.split(" ")
    name = parts[0]
    old = parts[1].to_i
    birth = parts[2]
    state = parts[3]
    
    user = User.new(name, old, birth, state)
end

users = [] 

n.times do |i|
    input = gets.chomp
    users << parse_input(input) 
end

m = gets.to_i
#パターン1
menbers = []
n.times{|i|users[i].old == m ? menbers << users[i].name : menbers }

menbers.map{|val| puts val}

#パターン2
menbers = users.select { |user| user.old == m }.map(&:name)
menbers.each { |name| puts name }
  • 年齢の取得のタイミングが後になってしまうので配列2回回してしまう
なかじなかじ

年齢で比較

問題

#入力例
3
mako 13 08/08 nara
taisei 16 12/04 nagano
megumi 14 11/02 saitama

#出力例
mako 13 08/08 nara
megumi 14 11/02 saitama
taisei 16 12/04 nagano

回答

n = gets.to_i
class User
    attr_accessor :name, :age, :birth, :state
    
    def initialize(name, age, birth, state)
        @name = name
        @age = age
        @birth = birth
        @state = state
    end
    
    def to_s
        "#{@name} #{@age} #{@birth} #{@state}"
    end
end
def parse_input(input)
    parts = input.split(" ")
    name = parts[0]
    age = parts[1].to_i
    birth = parts[2]
    state = parts[3]
    
    user = User.new(name, age, birth, state)
end

users = []

n.times do
    input = gets
    user = parse_input(input)
    users << user
end

sorted_users = users.sort_by { |user| user.age }

sorted_users.each do |user|
    puts user #=>勝手にto_sしてくれる
#=>mako 13 08/08 nara
#=>megumi 14 11/02 saitama
#=>taisei 16 12/04 nagano
end
なかじなかじ

構造体の書き換え

問題

#入力例
3 2
mako 13 08/08 nara
taisei 16 12/04 nagano
megumi 14 11/02 saitama
2 taihei
3 megu
#出力例
mako 13 08/08 nara
taihei 16 12/04 nagano
megu 14 11/02 saitama
  • 指定した要素を書き換えるよ〜

回答

n, m = gets.split(" ").map(&:to_i)
class User
    attr_accessor :name, :old, :birth, :state
    
    def initialize(name, old, birth, state)
        @name = name
        @old = old
        @birth = birth
        @state = state
    end
    
    def to_s
        "#{@name} #{@old} #{@birth} #{@state}"
    end
end

def parse_input(input)
    parts = input.split(" ")
    name = parts[0]
    old = parts[1].to_s
    birth = parts[2]
    state = parts[3]
    
    user = User.new(name, old, birth, state)
end


users = []
n.times do
    input = gets
    user = parse_input(input)
    users << user
end

m.times do
     parts = gets.split(" ")
     num = parts[0].to_i
     new_name = parts[1]
     users[num - 1].name = new_name
end

puts users

名前変更の箇所のメソッドを切り分ける(ChatGPT)

n, m = gets.split(" ").map(&:to_i)

class User
    attr_accessor :name, :old, :birth, :state
    
    def initialize(name, old, birth, state)
        @name = name
        @old = old
        @birth = birth
        @state = state
    end
    
    def change_name(new_name)
        @name = new_name
    end

    def to_s
        "#{@name} #{@old} #{@birth} #{@state}"
    end
end

def parse_input(input)
    parts = input.split(" ")
    name = parts[0]
    old = parts[1].to_s
    birth = parts[2]
    state = parts[3]
    
    User.new(name, old, birth, state)
end

users = []
n.times do
    input = gets
    user = parse_input(input)
    users << user
end

m.times do
    parts = gets.split(" ")
    num = parts[0].to_i
    new_name = parts[1]
    users[num - 1].change_name(new_name)  # 名前の変更をメソッドに委譲
end

puts users
なかじなかじ

条件に応じて出力変える

問題

#入力
7
make 2742 mako
getnum 1
make 2782 taisei
getname 2
make 31 megumi
getname 1
getname 3
#出力
2742
taisei
mako
megumi
  • makeならデータ作成
  • getnameなら名前取得
  • getnumなら番号取得

回答

n = gets.to_i
class Employee
    attr_accessor :number, :name
    def initialize(number, name)
        @number = number
        @name = name
    end
end

def data_input(input)
    number = input[1]
    name = input[2]
    Employee.new(number,name)
end

employees = []
n.times do
    input = gets.split(" ")
    if input[0] == "make"
        employee = data_input(input)
        employees << employee
    elsif input[0] == "getname"
        num = input[1].to_i - 1
        puts employees[num].name
    else
        num = input[1].to_i - 1
        puts employees[num].number
    end
end
  • 配列代入だからdef data_input(*input)にしたら、メソッドの中で二次元配列になっていた

改善版(ChatGPTにもらったの少し直した)

n = gets.to_i

class Employee
  attr_accessor :number, :name
  def initialize(number, name)
    @number = number
    @name = name
  end
end

employees = []

n.times do
  input = gets.split(" ")
  command = input[0]
  
  case command
  when "make"
    number = input[1]
    name = input[2]
    employees << Employee.new(number, name) #インスタンスの作成これでいいな
  when "getname", "getnum"
    index = input[1].to_i - 1
      if command == "getname"
        puts employees[index].name
      else
        puts employees[index].number
      end
  end
end
なかじなかじ

値の変更も行う

n = gets.to_i
class Employee
    attr_accessor :name, :number
    
    def initialize(name, number)
        @name = name
        @number = number
    end
end

employees = []
n.times do
    input = gets.split(" ")
    command = input[0]
    
    case command
    when "make"
        number = input[1].to_i
        name = input[2]
            employees << Employee.new(name, number)
    when "getname", "getnum"
    output = input[1].to_i - 1
        if command == "getname"
            puts employees[output].name
         else
            puts employees[output].number
        end
    when "change_num", "change_name"
    num = input[1].to_i - 1
    change = input[2]
        if command == "change_name"
            employees[num].name = change
         else
            employees[num].number = change.to_i
        end
    end
end
  • 書いててわかるごちゃごちゃや

リファクタリング(ChatGPT)

class Employee
    attr_accessor :name, :number
    
    def initialize(name, number)
        @name = name
        @number = number
    end
end

# 社員を作成
def make_employee(employees, number, name)
    employees << Employee.new(name, number)
end

# 社員情報を取得
def get_employee_info(employees, command, index)
    employee = employees[index]
    case command
    when "getname"
        puts employee.name
    when "getnum"
        puts employee.number
    end
end

# 社員情報を変更
def change_employee_info(employees, command, index, change)
    employee = employees[index]
    case command
    when "change_name"
        employee.name = change
    when "change_num"
        employee.number = change.to_i
    end
end

n = gets.to_i
employees = []

n.times do
    input = gets.split(" ")
    command, index = input[0], input[1].to_i - 1

    case command
    when "make"
        make_employee(employees, input[1].to_i, input[2])
    when "getname", "getnum"
        get_employee_info(employees, command, index)
    when "change_name", "change_num"
        change_employee_info(employees, command, index, input[2])
    end
end
  • メソッドで書き分けるの優秀すぎる〜〜
なかじなかじ

継承

  • ChatGPTのコードを元に変更をして学習(元のコードはCustomerクラスが別であってそれを成人と未成年で継承する感じだった)
  • superって便利だなぁ
class MinorCustomer
  attr_reader :total_amount

  def initialize(age)
    @age = age
    @total_amount = 0
  end

  def order_food(amount)
    @total_amount += amount
  end

  def order_softdrink(amount)
    @total_amount += amount
  end

  def order_alcohol(_amount)
    # 未成年なのでお酒の注文はキャンセルされる
    # こちらの引数はキャンセルになるので_がついている
  end
end

class AdultCustomer < MinorCustomer
  def initialize(age)
    super(age)
    # 成人はお酒割引があるので追加でインスタンス変数の設定が必要
    @alcohol_discount = false
  end

  def order_alcohol(amount)
    # 成人はお酒を注文できる
    @total_amount += amount
    @alcohol_discount = true
  end

  def order_food(amount)
    # お酒を頼んだ場合は200円引き
    if @alcohol_discount
      amount -= 200
    end
    super(amount)
  end
end

# 計算のためのメソッド
def calculate_total(customers, orders)
  orders.each do |order|
    # 分割代入
    customer_id, order_type, amount = order
    customer = customers[customer_id - 1]  # インデックスは0始まり

    case order_type
    when 'food'
      customer.order_food(amount)
    when 'softdrink'
      customer.order_softdrink(amount)
    when 'alcohol'
      customer.order_alcohol(amount)
    end
  end

  customers.map(&:total_amount)
end

# 標準入力から値を取得
n_customers, n_orders = gets.split.map(&:to_i)
# 年齢の一覧を取得
ages = n_customers.times.map { gets.to_i }
# thenはsplit の結果として得られた配列を次のブロックに渡す。下記と同じ。
# orders = n_orders.times.map do
#   o = gets.split
#   [o[0].to_i, o[1], o[2].to_i]
# end
orders = n_orders.times.map { gets.split.then { |o| [o[0].to_i, o[1], o[2].to_i] } }

# お客さんを初期化
customers = ages.map do |age|
  if age >= 20
    AdultCustomer.new(age)
  else
    MinorCustomer.new(age)
  end
end

# 会計を計算
result = calculate_total(customers, orders)

# 出力
result.each { |total| puts total }
なかじなかじ

デフォルト値あり

class MinorCustomer
    attr_reader :total_amount
    
    def initialize(age)
        @age = age
        @total_amount = 0
    end
    
    def order_food(amount)
        @total_amount += amount
    end
    
    def order_softdrink(amount)
        @total_amount += amount
    end
    
    def order_alcohol(_amount)
    end
end
class AdultCustomer < MinorCustomer
    def initialize(age)
        super(age) #ここのsuperがageのみで良いのはなぜだ(引数があるやつだけでいいのか)
        @alcohol_discount = false
    end
    
    def order_food(amount)
        if @alcohol_discount
            amount -= 200
        end
    super(amount)
    end
    
    def order_alcohol(amount:  500)
        @total_amount += amount
        @alcohol_discount = true
    end
end

def calculate_total(customers, orders)
    orders.each do |order|
        customer_id, order_type, amount = order
        customer = customers[customer_id - 1]
        
        case order_type
            when 'alcohol', "0"
              customer.order_alcohol(amount: amount || 500)
            when 'food'
              customer.order_food(amount)
            when 'softdrink'
              customer.order_softdrink(amount)
        end
    end
    customers.map(&:total_amount)
end

n_customers, n_orders = gets.split(" ").map(&:to_i)

ages = n_customers.times.map { gets.to_i }
orders = n_orders.times.map do
  o = gets.split
  [o[0].to_i, o[1], o[2] == nil ? nil : o[2].to_i]
end

customers = ages.map do |age|
  if age >= 20
    AdultCustomer.new(age)
  else
    MinorCustomer.new(age)
  end
end
result = calculate_total(customers, orders)
result.map{|total| puts total}

ordersのamoutの変換について

  • [[2, "0", nil], [2, "food", "4333"], [1, "0", nil], [2, "0", nil], [1, "food", "4606"]]
  • 上記の配列の3つ目の要素を、数値の場合はIntegernilの場合はnilのままにしてアルコール購入時のデフォルト値を渡せるようにしたかった
  • 便利な三項演算子
[o[0].to_i, o[1], o[2] == nil ? nil : o[2].to_i]

デフォルト値の渡し方

    def order_alcohol(amount:  500)
        @total_amount += amount
        @alcohol_discount = true
    end
#...
customer.order_alcohol(amount)
#...
  • 上記だとMain.rb:33:in order_alcohol': wrong number of arguments (given 1, expected 0) (ArgumentError)`のエラーが出る
    def order_alcohol(amount =  500)
        @total_amount += amount
        @alcohol_discount = true
    end
#...
customer.order_alcohol(amount)
#...
  • 上記だとMain.rb:34:in +': nil can't be coerced into Integer (TypeError)`のエラーが出る

解決策1

  • そもそもメソッドにnilが渡らないようにする
  • これなら別にデフォルト値の設定不要(nilなら500が引数になる)
#...
    def order_alcohol(amount)
        @total_amount += amount
        @alcohol_discount = true
    end
#...
     when 'alcohol', "0"
    customer.order_alcohol(amount || 500)
#...
  • キーワード引数バージョンもあり(1つなので微妙ですが)
#...
    def order_alcohol(amount:)
        @total_amount += amount
        @alcohol_discount = true
    end
#...
     when 'alcohol', "0"
    customer.order_alcohol(amount: amount || 500)
#...

解決策2

  • そもそも条件分岐を分ける
  #...
      def order_alcohol(_amount = 500) #ここにもデフォルト値置いとかないとエラーになる
    end
end
 #...
    def order_alcohol(amount = 500)
        @total_amount += amount
        @alcohol_discount = true
    end
  #...
          case order_type
            when 'alcohol'
              customer.order_alcohol(amount)
            when "0"
              customer.order_alcohol
            when 'food'
              customer.order_food(amount)
            when 'softdrink'
              customer.order_softdrink(amount)
        end

when節で複数の条件書くとき

  • when 'alcohol', "0"といったように並列で書く
なかじなかじ

辞書

クラスでやる

#入力値
2
Kirishima 1
Kyoko 2
Kirishima
#出力値
1
class Person
    attr_accessor :person_id, :property
    
    def initialize(person_id, property)
        @person_id = person_id
        @property = property
    end
end
n = gets.to_i
member = []
n.times do
    o = gets.split
    property, person_id = o[1], o[0]
    member << Person.new(property, person_id)
end
m = gets.chomp
puts member.select { |s| s.property == m }.map(&:person_id)
  • 最後の行をputs menber.map{|s| s.person_id if s.property == m }って書いたら違うと言われた
  • ひとまず条件でselectしてオブジェクトを確定してから、該当するもののperson_idを呼び出す流れ

ハッシュで書く

n = gets.to_i

properties = {}
n.times do
  s, a = gets.split(' ')
  properties[s] = a.to_i
end

s = gets.chomp #chompしないと改行コード含まれる
puts properties[s]

別バージョン

n = gets.to_i
properties = {}
n.times do
    s = gets.chomp
    properties[s] = 0
end
m = gets.to_i
m.times do
    s, a = gets.split(" ")
    properties[s] += a.to_i
end
l = gets.chomp
puts properties[l]
  • キーをシンボルにすべきでしょうか...

アルファベット降順

N = gets.to_i
members = {}
N.times { members[gets.chomp] = 0 }

M = gets.to_i
M.times do
  s, a = gets.split
  members[s] += a.to_i
end

puts members.sort.to_h.values
なかじなかじ

ハッシュの配列

  • 要素が二つある場合の表作るのに便利
  • 結構ごちゃごちゃになりますが...
n.times do |i|
 lines_num = i + 1    
  arrays["arry#{lines_num}"] = [] 
    lines = gets.split(" ").map(&:to_i)
    arrays["arry#{lines_num}"] << lines
end
なかじなかじ

10000以上で13で割れるものを探す

  • あんまりrubyで配列以外の繰り返し処理書いてこなかったので新鮮だ

whileバージョン

n = 10_000
while true
  break if n % 13 == 0
  n += 1
end
puts n

untilバージョン

number = 10000
until number % 13 == 0 do
  number += 1
end
p number
なかじなかじ

辞書ファイナル

失敗

A, B, C = gets.split(" ").map(&:to_i)
job_members = [] # 配列に変更
A.times do
    a, b =  gets.split(" ").map(&:to_i)
      job_member = {} 
      job_member["A"] = a
      job_member["B"] = b
      job_members << job_member 
end
job_members.each do |hash|
  # 1つ目と2つ目の値を入力
  b_value, c_value = gets.split.map(&:to_i)
  
  # "B"の値と入力された1つ目の値が一致する場合に"C"を追加
  if hash["B"] == b_value
    hash["C"] = c_value
  end
end

sorted_job_members = job_members.sort_by { |hash| hash["A"] }
sorted_job_members .each do |hash|
  puts "#{hash['A']} #{hash['C']}"
end

正解

  • 最後の行が重要
  • group_b[group_a[i + 1]]でgroup_aのバリューをキーを0から順番に呼び出し、呼び出したgroup_aのバリューがキーになるgroup_bの値を呼び出すことで紐付ける
  • 頭こんがらがる
n, m, l = gets.split(' ').map(&:to_i)

group_a = {} #AからB
n.times do
  num, req = gets.split(' ').map(&:to_i)
  group_a[num] = req
end
group_b = {} #BからC
m.times do
  num, req = gets.split(' ').map(&:to_i)
  group_b[num] = req
end

group_c = {} # 0回目:group_c[1]=>1 = group_b[group_a[1]] =>group_a[1]=2(キーが1のバリュー)=>group_b[2]=2!!!(キーが2のバリュー)
n.times { |i| group_c[i + 1] = group_b[group_a[i + 1]] }


group_c.each do |val|
  puts val.join(' ')
end
なかじなかじ

シミュレーション

  • 両方必要だった
h = gets.to_i

paiza = [0, 1, 1]
monster = [0, 1, 1]

h -= 2
turn = 2
while true
  # 代入
  monster[0] = monster[1]
  monster[1] = monster[2]

  paiza[0] = paiza[1]
  paiza[1] = paiza[2]

  monster[2] = paiza[1] + paiza[0]
  paiza[2] = monster[1] * 2 + monster[0]

  h -= paiza[2]
  turn += 1

  break if h <= 0
end

puts turn