Hash with two identical keys?

From Facets' multiton.rb (which is primarily Floran Franks' work), I'm

getting somthing that doesn't make any sense:

        POOLS[self] ||= {}
        p POOLS[self].class
        p POOLS[self].keys

Is outputing:

  Hash
  [[{:strip_comments=>false}], [{:strip_comments=>false}]]

How can two identical keys be in a hash?

T.

irb(main):005:0> bob = [{:whee => false}]
=> [{:whee=>false}]
irb(main):006:0> bob.hash
=> 23244868
irb(main):007:0> roger = [{:whee => false}]
=> [{:whee=>false}]
irb(main):008:0> roger.hash
=> 23235888
irb(main):009:0> bob.eql? roger
=> false
irb(main):010:0> bob[0].eql? roger[0]
=> false
irb(main):011:0> bob[0].hash
=> 23244870
irb(main):012:0> roger[0].hash
=> 23235890

I think it boils down to: there is no Hash#hash which inspects the
contents - Hash inherits Object#hash which is Object#object_id, and
the two hashes, while they appear to have the same contents, are not
the same object. So the keys are not "identical".

-A

···

On 12/26/06, Trans <transfire@gmail.com> wrote:

>From Facets' multiton.rb (which is primarily Floran Franks' work), I'm
getting somthing that doesn't make any sense:

        POOLS[self] ||= {}
        p POOLS[self].class
        p POOLS[self].keys

Is outputing:

  Hash
  [[{:strip_comments=>false}], [{:strip_comments=>false}]]

How can two identical keys be in a hash?

T.

Looks to me that your keys are arrays.

My guess is that

p POOLS[self].keys[0].class

will return Array, instead of Symbol, which is what you're probably
expecting. So, the hash doesn't have identical keys. It has two
different keys, each of which is a different array with identical
members. Try

p POOLS[self].keys[0].object_id
p POOLS[self].keys[1].object_id

and you'll see the keys really are different.

Trans wrote:

···

>From Facets' multiton.rb (which is primarily Floran Franks' work), I'm
getting somthing that doesn't make any sense:

        POOLS[self] ||= {}
        p POOLS[self].class
        p POOLS[self].keys

Is outputing:

  Hash
  [[{:strip_comments=>false}], [{:strip_comments=>false}]]

How can two identical keys be in a hash?

T.

hi trans-

afaik multiton.rb is mine

   http://codeforpeople.com/lib/ruby/multiton/multiton-1.0.2/lib/multiton.rb

the logic behind POOLS is that objects are cached this way

   POOLS[ class_of_object ][ args_given_to_new ] = obj

in otherwords, contructing two objects with the same argument lists will
contruct only one object. it's the argument lists which are used to determine
uniqueness. alternatively uniqueness will be determined via the method
'multiton_id' if you're class has implimented that instance method or the
object in question responds to it otherwise. so, in your case, you might use
something like

   hash.to_a.sort.hash

or something unique like that. eg

   class MyClass
     include Multiton

     attr :multiton_id

     def initialize h = {}
       @multiton_id = h.to_a.sort.hash
       super
     end
   end

kind regards.

-a

···

On Wed, 27 Dec 2006, Trans wrote:

From Facets' multiton.rb (which is primarily Floran Franks' work), I'm

getting somthing that doesn't make any sense:

       POOLS[self] ||= {}
       p POOLS[self].class
       p POOLS[self].keys

Is outputing:

Hash
[[{:strip_comments=>false}], [{:strip_comments=>false}]]

How can two identical keys be in a hash?

T.

--
if you find yourself slandering anybody, first imagine that your mouth is
filled with excrement. it will break you of the habit quickly enough. - the
dalai lama

thanks Ara and Jon,

I see what your saying. I was using #== not #eql? in comparing the
keys. So I see why it's faling now. How do I get aorund this? I'm
caching object based on therr initialization paramaters, which has to
be an array. Is there a simpler way or do I have to do something like:

  class Parameters < Array
    alias :eql? :==
  end

T.

Somehow I got Florian Franks name attached to that. Well, unless
Florian's got something to say about it, I'll reattribute to you. Sorry
about that!

T.

···

ara.t.howard@noaa.gov wrote:

On Wed, 27 Dec 2006, Trans wrote:

>> From Facets' multiton.rb (which is primarily Floran Franks' work), I'm
> getting somthing that doesn't make any sense:
>
> POOLS[self] ||= {}
> p POOLS[self].class
> p POOLS[self].keys
>
> Is outputing:
>
> Hash
> [[{:strip_comments=>false}], [{:strip_comments=>false}]]
>
> How can two identical keys be in a hash?
>
> T.

hi trans-

afaik multiton.rb is mine

   http://codeforpeople.com/lib/ruby/multiton/multiton-1.0.2/lib/multiton.rb

Trans wrote:

thanks Ara and Jon,

I see what your saying. I was using #== not #eql? in comparing the
keys. So I see why it's faling now. How do I get aorund this? I'm
caching object based on therr initialization paramaters, which has to
be an array. Is there a simpler way or do I have to do something like:

  class Parameters < Array
    alias :eql? :==
  end

Ugh. Nothing like that works either. It's not using eql? or equal?, but
rather #hash (I guess that's actually what you were trying to say Ara).
This doen't make any sense to me. Why do identical strings and arrays
have the same #hash value but not hashes?

T.

Trans wrote:

thanks Ara and Jon,

I see what your saying. I was using #== not #eql? in comparing the
keys. So I see why it's faling now. How do I get aorund this? I'm
caching object based on therr initialization paramaters, which has to
be an array. Is there a simpler way or do I have to do something like:

  class Parameters < Array
    alias :eql? :==
  end

T.

How about association lists?

irb(main):029:0> a1=[[:strip,false]]
=> [[:strip, false]]
irb(main):030:0> a2=[[:strip,false]]
=> [[:strip, false]]
irb(main):031:0> h={}
=> {}
irb(main):032:0> h[a1] = 'foo'
=> "foo"
irb(main):033:0> h[a2] = 'bar'
=> "bar"
irb(main):034:0> h
=> {[[:strip, false]]=>"bar"}

Trans wrote:

thanks Ara and Jon,

I see what your saying. I was using #== not #eql? in comparing the
keys. So I see why it's faling now. How do I get aorund this? I'm
caching object based on therr initialization paramaters, which has to
be an array. Is there a simpler way or do I have to do something like:

  class Parameters < Array
    alias :eql? :==
  end

Ugh. Nothing like that works either. It's not using eql? or equal?, but
rather #hash (I guess that's actually what you were trying to say Ara).

A Hash only asks if one object is #eql? to another when they have the same #hash. You can't use a Hash as a Hash key because Hash#hash is not implemented that way.

This doen't make any sense to me. Why do identical strings and arrays
have the same #hash value but not hashes?

Likely because:

s = ''
s.hash

and:

a = []
a << a
a.hash

are easier to compute than:

h = {}
h[h] = h
h.hash

···

On Dec 26, 2006, at 11:22, Trans wrote:

--
Eric Hodel - drbrain@segment7.net - http://blog.segment7.net

I LIT YOUR GEM ON FIRE!

Trans wrote:

Trans wrote:
> thanks Ara and Jon,
>
> I see what your saying. I was using #== not #eql? in comparing the
> keys. So I see why it's faling now. How do I get aorund this? I'm
> caching object based on therr initialization paramaters, which has to
> be an array. Is there a simpler way or do I have to do something like:
>
> class Parameters < Array
> alias :eql? :==
> end

Ugh. Nothing like that works either. It's not using eql? or equal?, but
rather #hash (I guess that's actually what you were trying to say Ara).
This doen't make any sense to me. Why do identical strings and arrays
have the same #hash value but not hashes?

Nope. It doesn't even use #hash. So it must be using #object_id with an
exception for Strings and Arrays. Dissapointing to say the least.

I had to resort to recursively converting all hashes to arrays.

T.

William James wrote:

Trans wrote:
> thanks Ara and Jon,
>
> I see what your saying. I was using #== not #eql? in comparing the
> keys. So I see why it's faling now. How do I get aorund this? I'm
> caching object based on therr initialization paramaters, which has to
> be an array. Is there a simpler way or do I have to do something like:
>
> class Parameters < Array
> alias :eql? :==
> end
>
> T.

How about association lists?

Thanks William! That's what I did and worked (desipite inefficency).

T.

Trans wrote:

thanks Ara and Jon,

Oops. Just saw that was Alex, not Ara, sorry about that Alex! And
thanks for the help.

T.

Eric Hodel wrote:

> Trans wrote:
>> thanks Ara and Jon,
>>
>> I see what your saying. I was using #== not #eql? in comparing the
>> keys. So I see why it's faling now. How do I get aorund this? I'm
>> caching object based on therr initialization paramaters, which has to
>> be an array. Is there a simpler way or do I have to do something
>> like:
>>
>> class Parameters < Array
>> alias :eql? :==
>> end
>
> Ugh. Nothing like that works either. It's not using eql? or equal?,
> but
> rather #hash (I guess that's actually what you were trying to say
> Ara).

A Hash only asks if one object is #eql? to another when they have the
same #hash. You can't use a Hash as a Hash key because Hash#hash is
not implemented that way.

I see. So it's not using object_id but

  a.hash == b.hash && a.eql?(b)

Is that right?

> This doen't make any sense to me. Why do identical strings and arrays
> have the same #hash value but not hashes?

Likely because:

s = ''
s.hash

and:

a = []
a << a
a.hash

are easier to compute than:

h = {}
h[h] = h
h.hash

Hmm... the expection gums up the works.

T.

···

On Dec 26, 2006, at 11:22, Trans wrote:

Trans wrote:

William James wrote:
> Trans wrote:
> > thanks Ara and Jon,
> >
> > I see what your saying. I was using #== not #eql? in comparing the
> > keys. So I see why it's faling now. How do I get aorund this? I'm
> > caching object based on therr initialization paramaters, which has to
> > be an array. Is there a simpler way or do I have to do something like:
> >
> > class Parameters < Array
> > alias :eql? :==
> > end
> >
> > T.
>
> How about association lists?

Thanks William! That's what I did and worked (desipite inefficency).

T.

Here's a speed comparison for various numbers of keys:

require 'benchmark'

$iterations = 40_000

def rand_sym
  letters = ('a'..'z').to_a
  sym = ""
  8.times{ sym << letters[ rand(letters.size) ] }
  sym.to_sym
end

def test_assoc n
  alist = []
  keys = []
  while alist.size < n do
    key = rand_sym
    unless keys.include?( key )
      alist << [ key, true ]
      keys << key
    end
  end
  $iterations.times{
    keys.each{|key| fail if alist.assoc(key)[1] != true }
  }
end

def test_hash n
  hash = {}
  keys = []
  while hash.size < n
    key = rand_sym
    unless hash.include?( key )
      hash[key] = true
      keys << key
    end
  end
  $iterations.times{
    keys.each{|key| fail if hash[key] != true }
  }
end

Benchmark.bm(8) do |x|
  [1,2,3,7,20].each{ |n|
    x.report("assoc %2d" % n) { test_assoc n }
    x.report("hash %2d" % n) { test_hash n }
  }
end

              user system total real
assoc 1 0.150000 0.000000 0.150000 ( 0.171000)
hash 1 0.130000 0.000000 0.130000 ( 0.140000)
assoc 2 0.291000 0.000000 0.291000 ( 0.310000)
hash 2 0.200000 0.000000 0.200000 ( 0.231000)
assoc 3 0.440000 0.000000 0.440000 ( 0.460000)
hash 3 0.291000 0.000000 0.291000 ( 0.311000)
assoc 7 1.172000 0.000000 1.172000 ( 1.252000)
hash 7 0.590000 0.000000 0.590000 ( 0.640000)
assoc 20 5.188000 0.000000 5.188000 ( 5.578000)
hash 20 1.652000 0.000000 1.652000 ( 1.813000)

William James wrote:

Here's a speed comparison for various numbers of keys:

require 'benchmark'

$iterations = 40_000

def rand_sym
  letters = ('a'..'z').to_a
  sym = ""
  8.times{ sym << letters[ rand(letters.size) ] }
  sym.to_sym
end

def test_assoc n
  alist = []
  keys = []
  while alist.size < n do
    key = rand_sym
    unless keys.include?( key )
      alist << [ key, true ]
      keys << key
    end
  end
  $iterations.times{
    keys.each{|key| fail if alist.assoc(key)[1] != true }
  }
end

def test_hash n
  hash = {}
  keys = []
  while hash.size < n
    key = rand_sym
    unless hash.include?( key )
      hash[key] = true
      keys << key
    end
  end
  $iterations.times{
    keys.each{|key| fail if hash[key] != true }
  }
end

Benchmark.bm(8) do |x|
  [1,2,3,7,20].each{ |n|
    x.report("assoc %2d" % n) { test_assoc n }
    x.report("hash %2d" % n) { test_hash n }
  }
end

              user system total real
assoc 1 0.150000 0.000000 0.150000 ( 0.171000)
hash 1 0.130000 0.000000 0.130000 ( 0.140000)
assoc 2 0.291000 0.000000 0.291000 ( 0.310000)
hash 2 0.200000 0.000000 0.200000 ( 0.231000)
assoc 3 0.440000 0.000000 0.440000 ( 0.460000)
hash 3 0.291000 0.000000 0.291000 ( 0.311000)
assoc 7 1.172000 0.000000 1.172000 ( 1.252000)
hash 7 0.590000 0.000000 0.590000 ( 0.640000)
assoc 20 5.188000 0.000000 5.188000 ( 5.578000)
hash 20 1.652000 0.000000 1.652000 ( 1.813000)

Nice. Doesn't matter a whole lot a few keys but there is a clear slow
down.

I came up with another possibility however. I won;t work for all cases,
but using Marshal.dup on the args instead of converting to assoc gives
the proper result too. Wonder how that would benchmark?

T.