ゼロからわかる Ruby 超入門 (かんたんIT基礎講座)を参考書にしながらRubyを勉強していて学んだことを書いています。
ハッシュを使ってifを書き換える
ペアプロでやっていて色々教えていただいたのでシェアします!メソッドについてまとめた7章の章末問題7-4の問6。
品物の値段を表すpriceメソッドを定義し、キーワード引数でitemを渡す。
itemがコーヒーの時は300を、カフェラテの時は400を戻り値として返す。
という処理を書く問題。何の気なしにこう書きました
def price(item:)
if item == "コーヒー"
return 300
elsif item == "カフェラテ"
return 400
end
end
puts price(item: "コーヒー") #=> 300
puts price(item: "カフェラテ") #=> 400
でも次の問7で、さらにキーワード引数としてsizeがでてきます。
sizeによって価格を上乗せしていく
sizeがショートの時は+0円、トールの時+50円、ベンティの時+100円
以上の条件が新たに加わります。
こうなってくると、ifとか、case & whenで書いてるとコードが長くなるし条件の追加とかダルそう。。。でもこれしか思いつかなかった。
# caseで書いた例
def price(item:, size:)
case item
when "コーヒー"
price = 300
when "カフェラテ"
price = 400
end
case size
when "ショート"
price += 0
when "トール"
price += 50
when "ベンティ"
price += 100
end
end
puts price(item: "コーヒー", size: "ベンティ") #=> 400
解答例を見てみると、メソッドの中で条件分岐させるのではなく、ハッシュを使ってスッキリ書いています。
# ハッシュを使おう!
def price(item:, size: "ショート")
items = {"コーヒー" => 300, "カフェラテ" => 400}
sizes = {"ショート" => 0, "トール" => 50, "ベンティ" => 100}
items[item] + sizes[size]
end
puts price(item: "コーヒー", size: "トール") #=> 350
この書き方の良いところは、priceメソッドの中では決まった入力に決まった値を返す、という処理だけが与えられているところです。if文などで条件分岐させるよりも拡張性が高く、コードの見通しも良いです。
存在しないキーが引数として渡されると…
スッキリ書けて満足なのですが、実はまだ足りません。先ほどのコードだと例えば最後の行を、puts price(item: "ミルクティー", size: "トール")
とすると、エラーになります。
# errorメッセージ
`price': undefined method `+' for nil:NilClass (NoMethodError)`
何が起こっているかというと、
ミルクティーという存在しないキーを引数に指定しているために、
items[item] + sizes[size]
の中身が、nil + 50
になっているエラーメッセージのとおり、nilの所属するNilClassには'+'メソッドは定義されていないのでエラーですと怒られる
という流れです。 個人的には+メソッドが定義されている
というのが面白く感じました。NilClassについて調べてみることにします。
NilClassについて調べてみた
# クラスを調べてみる
irb(main):004:0> nil.class
=> NilClass
irb(main):005:0> 1.class
=> Integer
irb(main):006:0> "1".class
=> String
nilはNilClass、面白い。+メソッドを使えるか調べてみます
# +メソッドが存在するか調べる
irb(main):015:0> nil.respond_to?(:+)
=> false
irb(main):016:0> 1.respond_to?(:+)
=> true
irb(main):017:0> "1".respond_to?(:+)
=> true
IntegerやStringでは+使えるけど、NilClassでは+は使えない。NilClassの全部のメソッドをみてみます。たしかに :+ は存在しない。
irb(main):019:0> nil.methods
=> [:&, :inspect, :to_a, :to_s, :===, :to_f, :to_i, :=~, :to_h, :nil?, :to_r,
:rationalize, :|, :to_c, :^, :instance_variable_defined?, :remove_instance_variable,
:instance_of?, :kind_of?, :is_a?, :tap, :instance_variable_set, :protected_methods,
:instance_variables, :instance_variable_get, :public_methods, :private_methods,
:method, :public_method, :public_send, :singleton_method, :define_singleton_method,
:extend, :to_enum, :enum_for, :<=>, :!~, :eql?, :respond_to?, :freeze, :object_id,
:send, :display, :hash, :class, :singleton_class, :clone, :dup, :itself, :yield_self,
:then, :taint, :tainted?, :untaint, :untrust, :untrusted?, :trust, :frozen?, :methods,
:singleton_methods, :equal?, :!, :==, :instance_exec, :!=, :instance_eval, :__id__,
:__send__]
存在しないキーが渡された時の処理を考える
ここから本題。存在しないキーが指定された時の処理方法をいくつか考えます。
nilを.to_iで変換して+メソッドを使えるようにする。
def price(item:, size: "ショート")
items = {"コーヒー" => 300, "カフェラテ" => 400}
sizes = {"ショート" => 0, "トール" => 50, "ベンティ" => 100}
items[item].to_i + sizes[size] # 中身は 0 + 50
end
puts price(item: "ミルクティー", size: "トール") #=> 50
ただし.to_iで変換するとnilは0になるので、0として処理してOKな場合に限ります。
ハッシュにデフォルト値を渡す
いくつかやり方がありますがとりあえず1つだけ。下記以外にHash.new()として値を渡すこともできます。
# 元のハッシュitemsに.fetchを使って、デフォルトの値を渡しておく
def price(item:, size: "ショート")
items = {"コーヒー" => 300, "カフェラテ" => 400}
sizes = {"ショート" => 0, "トール" => 50, "ベンティ" => 100}
items.fetch(item, 350) + sizes[size]
end
puts price(item: "ミルクティー", size: "トール") #=> 400
.fetchは、第二引数にデフォルト値を一緒に渡すことができます。以下リファレンスより。
fetch(key, default = nil) {|key| ... } -> object[permalink][rdoc]
key に関連づけられた値を返します。該当するキーが登録されてい ない時には、引数 default が与えられていればその値を、ブロッ クが与えられていればそのブロックを評価した値を返します。
論理演算子で、nil(つまり偽)のときの値を設定しておく
# items[item]がnil、つまり条件が偽のときの値を用意して渡す
def price(item:, size: "ショート")
items = {"コーヒー" => 300, "カフェラテ" => 400}
sizes = {"ショート" => 0, "トール" => 50, "ベンティ" => 100}
(items[item] || 350) + sizes[size]
end
puts price(item: "ミルクティー", size: "トール") #=> 400
こんな感じで掘り下げていくと、とても面白いです。