独自に作ったクラスを何らかの基準で「同じもの」と判断させtallyメソッドで集計したい。
ここではDrinkクラスを定義しname(商品名)とprice(値段)がそれぞれ等しいなら「同じ(同値)」だとみなしてドリンクが何本あるか集計したいとする。
やりたいことは以下の通り
Array.new(5) { Drink.new('リンゴジュース', 150) }.tally #=> {<<リンゴジュース>>=>5}
普通にクラスを定義してtallyメソッドを読んでも「同じ」とは判断してくれないのでうまくいかない。
class Drink attr_reader :name, :price def initialize(name, price) @name = name @price = price end def inspect "<<#{name}>>" end end Array.new(5) { Drink.new('リンゴジュース', 150) }.tally # => { # => <<リンゴジュース>>=>1, # => <<リンゴジュース>>=>1, # => <<リンゴジュース>>=>1, # => <<リンゴジュース>>=>1, # => <<リンゴジュース>>=>1, # => <<リンゴジュース>>=>1 # => }
ちなみに Array.new(5, Drink(..) }
みたいな呼び出しをすると集計はされるがオブジェクトIDが等しくなるので本意ではないとします。
tallyはeql?メソッドやhashメソッドを用いて「同じ」と判定している。 なのでこれらのメソッドを適切にオーバーライドする。今回は商品名(name)が等しい かつ 値段が等しいものは「同じ」ものとして集計したいので以下のようなコードになる。
class Drink attr_reader :name, :price def initialize(name, price) @name = name @price = price end private def inspect "<<#{name}>>" end def eql?(other) name == other.name && price == other.price end def hash [price, name].hash end end Array.new(5) { Drink.new('リンゴジュース', 150) }.tally #=> {<<リンゴジュース>>=>5}
めでたし。めでたし。
参考
- hashのドキュメント。hash値を定めるときの注意点が書いてあります。
- eql?のドキュメント
- tallyのドキュメント
備考
仲間とモブプロしているときに思いついてあれこれしてたらうまくいきました。いろいろアドバイスをくれた仲間に感謝します。