[lnkForumImage]
TotalShareware - Download Free Software

Confronta i prezzi di migliaia di prodotti.
Asp Forum
 Home | Login | Register | Search 


 

Forums >

comp.lang.ruby

hash of hashes w/defaults doesn't add keys as expected

rpardee@gmail.com

3/11/2009 3:32:00 PM

Hey All,

This surprised me:

hash_hash = Hash.new(Hash.new(0))

hash_hash[:foo][:bar] += 1
hash_hash[:foo][:baz] += 1

puts(hash_hash[:foo].keys.inspect) # => [:baz, :bar]
puts(hash_hash.keys.inspect) # => [] ?

Shouldn't hash_hash.keys == [:foo]? I'm running ruby 1.8.6
(2007-03-13 patchlevel 0) [i386-mswin32].

If this is expected behavior, what's the easiest/best performing way
to make sure those keys are added?

Many thanks!

-Roy
3 Answers

Jesús Gabriel y Galán

3/11/2009 3:52:00 PM

0

On Wed, Mar 11, 2009 at 4:33 PM, rpardee@gmail.com <rpardee@gmail.com> wrot=
e:
> Hey All,
>
> This surprised me:
>
> =A0hash_hash =3D Hash.new(Hash.new(0))
>
> =A0hash_hash[:foo][:bar] +=3D 1
> =A0hash_hash[:foo][:baz] +=3D 1
>
> =A0puts(hash_hash[:foo].keys.inspect) =A0# =3D> [:baz, :bar]
> =A0puts(hash_hash.keys.inspect) =A0 =A0 =A0 =A0# =3D> [] =A0?
>
> Shouldn't hash_hash.keys =3D=3D [:foo]? =A0I'm running ruby 1.8.6
> (2007-03-13 patchlevel 0) [i386-mswin32].
>

The idiom you are using to construct the hash specifies what the hash
*returns* when accesing a non-existing key.
It doesn't set that value for that key in the hash:

irb(main):001:0> a =3D Hash.new(0)
=3D> {}
irb(main):002:0> a[2]
=3D> 0
irb(main):003:0> a
=3D> {}

You example:

hash_hash[:foo][:bar] +=3D 1

Could be split in several parts, so that you better understand what's going=
on:

hash_hash[:foo] =3D> this returns the default object (which is a hash),
but doesn't create an entry in the hash. For simplicity in the
example, let's call this object default_object. Your code is
equivalent then to:

default_object[:bar] =3D default_object[:bar] + 1

(because of the +=3D). The right hand side default_object[:bar] returns
0, which is the default object of the default_object. Plus 1 makes 1,
and this is assigned to the key :bar in the default object. That's why
you see :bar =3D> 1 when you access a non-existing key in the hash_hash,
because you have modified the default object. The hash_hash is not
modified with any extra entry, though.

> If this is expected behavior, what's the easiest/best performing way
> to make sure those keys are added?

You want the block form of Hash.new:

irb(main):009:0> hash_hash =3D Hash.new {|h,k| h[k] =3D Hash.new(0)}
=3D> {}
irb(main):010:0> hash_hash[:foo][:bar] +=3D 1
=3D> 1
irb(main):011:0> hash_hash
=3D> {:foo=3D>{:bar=3D>1}}

If you want an infinitely nested hash, this is a neat trick:

irb(main):012:0> nested_hash =3D Hash.new {|h,k| h[k] =3D Hash.new &h.defau=
lt_proc}
=3D> {}
irb(main):013:0> nested_hash[:a][:b][:c][:d] =3D 1
=3D> 1
irb(main):014:0> nested_hash
=3D> {:a=3D>{:b=3D>{:c=3D>{:d=3D>1}}}}

Hope this helps,

Jesus.

Rob Biedenharn

3/11/2009 4:13:00 PM

0

On Mar 11, 2009, at 11:52 AM, Jes=FAs Gabriel y Gal=E1n wrote:

> On Wed, Mar 11, 2009 at 4:33 PM, rpardee@gmail.com =20
> <rpardee@gmail.com> wrote:
>> Hey All,
>>
>> This surprised me:
>>
>> hash_hash =3D Hash.new(Hash.new(0))
>>
>> hash_hash[:foo][:bar] +=3D 1
>> hash_hash[:foo][:baz] +=3D 1
>>
>> puts(hash_hash[:foo].keys.inspect) # =3D> [:baz, :bar]

Jesus's answer made me realize the true behavior (I was scratching my =20=

head on this one a bit).

Perhaps this will cement it:

hash_hash[:notfoo].keys

It gets the same default object for the missing :notfoo key as it got =20=

for the missing :foo.

-Rob

>>
>> puts(hash_hash.keys.inspect) # =3D> [] ?
>>
>> Shouldn't hash_hash.keys =3D=3D [:foo]? I'm running ruby 1.8.6
>> (2007-03-13 patchlevel 0) [i386-mswin32].
>>
>
> The idiom you are using to construct the hash specifies what the hash
> *returns* when accesing a non-existing key.
> It doesn't set that value for that key in the hash:
>
> irb(main):001:0> a =3D Hash.new(0)
> =3D> {}
> irb(main):002:0> a[2]
> =3D> 0
> irb(main):003:0> a
> =3D> {}
>
> You example:
>
> hash_hash[:foo][:bar] +=3D 1
>
> Could be split in several parts, so that you better understand =20
> what's going on:
>
> hash_hash[:foo] =3D> this returns the default object (which is a =
hash),
> but doesn't create an entry in the hash. For simplicity in the
> example, let's call this object default_object. Your code is
> equivalent then to:
>
> default_object[:bar] =3D default_object[:bar] + 1
>
> (because of the +=3D). The right hand side default_object[:bar] =
returns
> 0, which is the default object of the default_object. Plus 1 makes 1,
> and this is assigned to the key :bar in the default object. That's why
> you see :bar =3D> 1 when you access a non-existing key in the =
hash_hash,
> because you have modified the default object. The hash_hash is not
> modified with any extra entry, though.
>
>> If this is expected behavior, what's the easiest/best performing way
>> to make sure those keys are added?
>
> You want the block form of Hash.new:
>
> irb(main):009:0> hash_hash =3D Hash.new {|h,k| h[k] =3D Hash.new(0)}
> =3D> {}
> irb(main):010:0> hash_hash[:foo][:bar] +=3D 1
> =3D> 1
> irb(main):011:0> hash_hash
> =3D> {:foo=3D>{:bar=3D>1}}
>
> If you want an infinitely nested hash, this is a neat trick:
>
> irb(main):012:0> nested_hash =3D Hash.new {|h,k| h[k] =3D Hash.new =20
> &h.default_proc}
> =3D> {}
> irb(main):013:0> nested_hash[:a][:b][:c][:d] =3D 1
> =3D> 1
> irb(main):014:0> nested_hash
> =3D> {:a=3D>{:b=3D>{:c=3D>{:d=3D>1}}}}
>
> Hope this helps,
>
> Jesus.
>

Rob Biedenharn http://agileconsult...
Rob@AgileConsultingLLC.com



rpardee@gmail.com

3/11/2009 5:52:00 PM

0

Aaaahhh--okay. I get it now. Trippy.

Many thanks Jesus & Rob!