Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Set with custom rule

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?

like image 838
apneadiving Avatar asked Apr 14 '12 13:04

apneadiving


1 Answers

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.

like image 63
Phrogz Avatar answered Sep 29 '22 20:09

Phrogz