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

R

rpardee

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
 
J

Jesús Gabriel y Galán

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.
 
R

Rob Biedenharn

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://agileconsultingllc.com
(e-mail address removed)
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,744
Messages
2,569,482
Members
44,901
Latest member
Noble71S45

Latest Threads

Top