Download Firefox: WindowsMac OS X
logo       
Google Custom Search
    AddThis Social Bookmark Button
-->

Re: how do I convince Hash that two keys are the same?: msg#00057

Subject: Re: how do I convince Hash that two keys are the same?
Quoting gsinclair@xxxxxxxxxxxxxxx, on Tue, Mar 15, 2005 at 08:26:24AM +1100:
> On Monday, March 14, 2005, 6:19:24 PM, Yukihiro wrote:
> 
> > |Would this be useful to others? What would a good example be?
> 
> > Might be.  Would someone in rdoc reference maintainers merge this if
> > he feels this one valuable?
> 
> > matz.
> 
> I think it's valuable and would like someone to commit it, once an
> example has been found.  I think a good example is
> 
>   class Details
>     attr_accessor :name, :age
>     def ==(other)
>       @name = other.name and @age = other.age
>     end
>     def hash
>       @name.to_s + "," + @age.to_s

The "," is just noise, and an extra string, isn't it? I think this
could be done as:

   @name.hash ^ @age.hash

I base that what Tanaka did in resolv.rb, but I just looked at what
Array does:

  h = self.length
  self.each { |i|
    h = (h << 1) | (h<0 ? 1 : 0)
    n = i.hash
    h ^= n
  }

(pseudo code xlated from C).

I assume the shifting business is so that [1,2].hash != [2,1].hash,
which your "," would have the effect of, as well.


> A good rule of thumb (which should be included in the documentation):
> if you define #== in a class, you should also define #hash.  It's
> "data" objects that typically need these, because they are the kinds
> of things that will be stored in hashes.

Thats an interesting way of looking at it, it sounds right.

Perhaps Object#== should have a note to that effect?


Perhaps a better/another way to demonstrate would be 

  class Name
    attr_reader :first, :last
    def initialize(first, last)
      @first = first.to_str, @last = last.to_str
    end
    def eql?(other)
      first.downcase == other.first.downcase && last == other.last.downcase
    end
    def ==(other)
      eql?(other)
    end
    def hash
      [first.downcase, last.downcase].hash
    end
  end

This has the property that a.eql? b => true implies a.hash == b.hash, as
Object#hash documents.

Also it has the #==, which is your suggested warning that a #hash should
be defined.

Also, it ensures that

  Name.new('Joe', 'smith').hash != Name.new('smith','joe').hash

Is that important? It's ok for two object that are not #eql? to hash
together, Hash will further distinguish them with #eql?, that's why it
needs it.

Maybe an example of the pitfalls of classes that say they are == and
eql? but hash to different values would be informative:

  irb(main):001:0> class Foo; def hash; 1; end; def eql?(o); true; end;
  end
  => nil
  irb(main):002:0>  class Bar; def hash; 2; end; def eql?(o); true; end;
  end
  => nil
  irb(main):003:0> h = {}
  => {}
  irb(main):004:0> h[Foo.new] = nil
  => nil
  irb(main):005:0> h
  => {#<Foo:0x34d2d8>=>nil}
  irb(main):006:0> h[Foo.new] = 2
  => 2
  irb(main):007:0> h
  => {#<Foo:0x34d2d8>=>2}
  irb(main):008:0> h[Bar.new] = 4
  => 4
  irb(main):009:0> h
  => {#<Foo:0x34d2d8>=>2, #<Bar:0x336b8c>=>4}
  irb(main):010:0> 


I'm looking at the docs for Object#==, and they have a good description
of this.

The difference between == and eql? is subtle. Perhaps a decent way of
thinking of the difference is whether you think that they should be the
same keys when used in a Hash. If so, eql? must be true.


Cheers,
Sam





<Prev in Thread] Current Thread [Next in Thread>