Set with custom rule

ruby, ruby-on-rails

Solution

The docs that you link to say explicitly (emphasis mine):

The equality of each couple of elements is determined according to `Object#eql?` and `Object#hash`, since `Set` uses `Hash` as storage.

If you add a `hash` method to your class that returns the same value for `eql?` objects, it works:

# With your current class

f1, f2 = Foo.new, Foo.new
p f1.eql?(f2)
#=> true
p f1.hash==f2.hash
#=> false
p Set[f1,f2].length
#=> 2

# Fix the problem
class Foo
  def hash
    [bar,hash].hash
  end
end

f1, f2 = Foo.new, Foo.new
p f1.eql?(f2)
#=> true
p f1.hash==f2.hash
#=> true
p Set[f1,f2].length
#=> 1

To be honest I've never had a great sense for how to write a good custom `hash` method when multiple values are involved.

Problem

According to the Set doc, elements in a set are compared using `eql?`. I have a class like: ``` class Foo attr_accessor :bar, :baz def initialize(bar = 1, baz = 2) @bar = bar @baz = baz end def eql?(foo) bar == foo.bar && baz == foo.baz end end ``` In console: ``` f1 = Foo.new f2 = Foo.new f1.eql? f2 #=> true ``` But... ``` s = Set.new s << f1 s << f2 s.size #=> 2 ``` Because `f1` equals `f2`, `s` should not include both of them. How to make the `set` reject elements with a custom rule?

Original source