Default values of hashes


#1

Hi,

Suppose I want to create a hash, which has a couple of elements with
strings as the keys and the values of each of the elements were hashes
themselves, and they each had a few elements, like this:

trt_hash = Hash.new( {} )

trt_hash[‘a’] = { :x => 0, :y => 0, :z => 0 }
trt_hash[‘b’] = { :x => 0, :y => 0, :z => 0 }

If I increment one of the values of the hash within the hash, like this,
it works fine:

trt_hash[‘a’][:z] += 100
puts trt_hash[‘Hi’][:z] = 10
puts trt_hash.inspect

I get: {“a”=>{:z=>100, :x=>0, :y=>0}, “b”=>{:z=>0, :x=>0, :y=>0}}, which
is what I expected.

But if I try to add another key to trt_hash and increment one of the
keys of its hash, like this:

trt_hash[‘c’][:z] += 100
puts trt_hash.inspect

there is no error, but there is also no reference to the ‘c’ key when I
print trt_hash.

Also, if I try this, I get an error:

trt_hash[‘a’][:w] += 100
puts trt_hash.inspect

The key above is ‘a’, which exists, but :w in the ‘a’ hash does not
exist, so I get the following error:

undefined method `+’ for nil:NilClass (NoMethodError)

I want to put able to add elements to trt_has at will and have them
default to an empty hash, and I want to be able to modify the hash
within the hash without predefining anything. I feel like this would
work if I set up the hash and the hash within the hash with the proper
defaults, but I do not know how to do that.

I thought if I defined trt_hash like this:

trt_hash = Hash.new( Hash.new(0))

I would be able to add any key to trt_hash and its value would have a
default of an empty hash, and then I’d be able to add any element I
wanted to the nested hash, and its default would be 0. But this does
not seem to work.

Any suggestions?

Sorry if this email is unclear. If it is, please let me know and I will
try to clarify.

Thanks!

Glenn


#2

Directly from the rdoc:

 "If a block is
  specified, it will be called with the hash object and the key, and
  should return the default value. It is the block's responsibility
  to store the value in the hash if required."

So you want:

irb> trt_hash = Hash.new {|h,k| h[k] = Hash.new &h.default_proc }
=> {}
irb> trt_hash[:a][:b][:c] = “hi”
=> “hi”
irb> trt_hash
=> {:a=>{:b=>{:c=>“hi”}}}

-Rob

Rob B. http://agileconsultingllc.com
removed_email_address@domain.invalid


#3

From: Glenn [mailto:removed_email_address@domain.invalid]

I would be able to add any key to trt_hash and its value

would have a default of an empty hash, and then I’d be able

to add any element I wanted to the nested hash, and its

default would be 0. But this does not seem to work.

…I thought if I defined trt_hash like this:

trt_hash = Hash.new( Hash.new(0))

that is near to your objective. you just have to build the hash since
defaults do not, ie use the block form to store value in hash
http://www.ruby-doc.org/core/classes/Hash.src/M002868.html

try eg,

trt_hash = Hash.new{|h,k| h[k]= Hash.new{|h2,k2| h2[k2]=0} }
=> {}
trt_hash[“a”]
=> {}
trt_hash
=> {“a”=>{}}
trt_hash[“a”][:x]
=> 0
trt_hash
=> {“a”=>{:x=>0}}
trt_hash[“a”][:x]+=100
=> 100
trt_hash
=> {“a”=>{:x=>100}}
trt_hash[“a”][:x]+=100
=> 200
trt_hash
=> {“a”=>{:x=>200}}
trt_hash[“a”][:w]+=100
=> 100
trt_hash
=> {“a”=>{:w=>100, :x=>200}}
trt_hash[“b”][:w]+=100
=> 100
trt_hash
=> {“a”=>{:w=>100, :x=>200}, “b”=>{:w=>100}}


#4

I actually have a followup question to this one.

It turns out that sometimes I will need to go more than two levels deep
with the hashes. I inferred that to go three levels deep, the code
would be:

h = Hash.new{|h,k| h[k]= Hash.new{|h2,k2| h2[k2]= Hash.new{|h3,k3|
h3[k3]=0}}}

This works fine. It seems, though, that I might have to go more levels
deep than this. I am trying to look for a way to do this dynamically
within a method. In other words, if I need 3 levels, it creates a hash
within hash object 3 levels deep (like in the line of code above) and if
I need 5, it creates one 5 levels deep. I do not know how to do this
(or even if it is possible). Any suggestions on this one?

Thanks,

Glenn


From: Glenn removed_email_address@domain.invalid
To: ruby-talk ML removed_email_address@domain.invalid
Sent: Saturday, January 3, 2009 9:29:37 PM
Subject: Re: Default values of hashes

Both solutions are great. I had something else that was sort of
working, but these are far more elegant.

Thanks!


From: “Peña, Botp” removed_email_address@domain.invalid
To: ruby-talk ML removed_email_address@domain.invalid
Sent: Saturday, January 3, 2009 12:03:35 AM
Subject: Re: Default values of hashes

From: Glenn [mailto:removed_email_address@domain.invalid]

I would be able to add any key to trt_hash and its value

would have a default of an empty hash, and then I’d be able

to add any element I wanted to the nested hash, and its

default would be 0. But this does not seem to work.

…I thought if I defined trt_hash like this:

trt_hash = Hash.new( Hash.new(0))

that is near to your objective. you just have to build the hash since
defaults do not, ie use the block form to store value in hash
http://www.ruby-doc.org/core/classes/Hash.src/M002868.html

try eg,

trt_hash = Hash.new{|h,k| h[k]= Hash.new{|h2,k2| h2[k2]=0} }
=> {}
trt_hash[“a”]
=> {}
trt_hash
=> {“a”=>{}}
trt_hash[“a”][:x]
=> 0
trt_hash
=> {“a”=>{:x=>0}}
trt_hash[“a”][:x]+=100
=> 100
trt_hash
=> {“a”=>{:x=>100}}
trt_hash[“a”][:x]+=100
=> 200
trt_hash
=> {“a”=>{:x=>200}}
trt_hash[“a”][:w]+=100
=> 100
trt_hash
=> {“a”=>{:w=>100, :x=>200}}
trt_hash[“b”][:w]+=100
=> 100
trt_hash
=> {“a”=>{:w=>100, :x=>200}, “b”=>{:w=>100}}


#5

Hi –

On Sun, 4 Jan 2009, Glenn wrote:

I actually have a followup question to this one.

It turns out that sometimes I will need to go more than two levels deep
with the hashes. I inferred that to go three levels deep, the code
would be:

h = Hash.new{|h,k| h[k]= Hash.new{|h2,k2| h2[k2]= Hash.new{|h3,k3|
h3[k3]=0}}}

This works fine. It seems, though, that I might have to go more
levels deep than this. I am trying to look for a way to do this
dynamically within a method. In other words, if I need 3 levels, it
creates a hash within hash object 3 levels deep (like in the line of
code above) and if I need 5, it creates one 5 levels deep. I do not
know how to do this (or even if it is possible). Any suggestions on
this one?

It’s come up before; here’s the solution I seem to recall:

hproc = lambda {|h,k| h[k] = Hash.new(&hproc) }
hash = Hash.new(&hproc)

hash[1][2][3] = 4
p hash # {1=>{2=>{3=>4}}}

David


David A. Black / Ruby Power and Light, LLC
Ruby/Rails consulting & training: http://www.rubypal.com
Coming in 2009: The Well-Grounded Rubyist (http://manning.com/black2)

http://www.wishsight.com => Independent, social wishlist management!


#6

Glenn wrote:

In other words, if I need 3 levels, it creates a hash within hash object 3
levels deep (like in the line of code above) and if I need 5, it creates
one 5 levels deep. I do not know how to do this (or even if it is
possible). Any suggestions on this one?

def nested_hash(levels, def_value=nil, &def_proc)
inner_hash = def_proc ? Hash.new(&def_proc) : Hash.new(def_value)
if levels == 1
inner_hash
else
l = [lambda {|h,k| h[k] = inner_hash}]
(levels-2).times do |i|
l[i+1] = lambda {|h,k| h[k] = Hash.new( &l[i] ) }
end
Hash.new(&l[-1])
end
end

This creates a nested hash levels levels deep, where the innermost hash
can
optionally have a default value or proc.

Usage:
h = nested_hash(1)

Same as h = Hash.new

h = nested_hash(1) {|h,k| h[k] = Array.new}

Same as h = Hash.new {|h,k| h[k] = Array.new}

h = nested_hash(3)

Same as h = Hash.new {|h,k| h[k] = Hash.new {|h,k| h[k] = Hash.new}}

h = nested_hash(3, :foo)

Same as h = Hash.new {|h,k| h[k] = Hash.new {|h,k| h[k] =

Hash.new(:foo)}}

HTH,
Sebastian


#7

Both solutions are great. I had something else that was sort of
working, but these are far more elegant.

Thanks!


From: “Peña, Botp” removed_email_address@domain.invalid
To: ruby-talk ML removed_email_address@domain.invalid
Sent: Saturday, January 3, 2009 12:03:35 AM
Subject: Re: Default values of hashes

From: Glenn [mailto:removed_email_address@domain.invalid]

I would be able to add any key to trt_hash and its value

would have a default of an empty hash, and then I’d be able

to add any element I wanted to the nested hash, and its

default would be 0. But this does not seem to work.

…I thought if I defined trt_hash like this:

trt_hash = Hash.new( Hash.new(0))

that is near to your objective. you just have to build the hash since
defaults do not, ie use the block form to store value in hash
http://www.ruby-doc.org/core/classes/Hash.src/M002868.html

try eg,

trt_hash = Hash.new{|h,k| h[k]= Hash.new{|h2,k2| h2[k2]=0} }
=> {}
trt_hash[“a”]
=> {}
trt_hash
=> {“a”=>{}}
trt_hash[“a”][:x]
=> 0
trt_hash
=> {“a”=>{:x=>0}}
trt_hash[“a”][:x]+=100
=> 100
trt_hash
=> {“a”=>{:x=>100}}
trt_hash[“a”][:x]+=100
=> 200
trt_hash
=> {“a”=>{:x=>200}}
trt_hash[“a”][:w]+=100
=> 100
trt_hash
=> {“a”=>{:w=>100, :x=>200}}
trt_hash[“b”][:w]+=100
=> 100
trt_hash
=> {“a”=>{:w=>100, :x=>200}, “b”=>{:w=>100}}


#8

Hi –

On Mon, 5 Jan 2009, Sebastian H. wrote:

else

h = nested_hash(3, :foo)

Same as h = Hash.new {|h,k| h[k] = Hash.new {|h,k| h[k] = Hash.new(:foo)}}

Why not make it just keep going automatically? (See my other post.)

David


David A. Black / Ruby Power and Light, LLC
Ruby/Rails consulting & training: http://www.rubypal.com
Coming in 2009: The Well-Grounded Rubyist (http://manning.com/black2)

http://www.wishsight.com => Independent, social wishlist management!


#9

Hi David,

In your other post, there is no default value for the innermost hash.

hproc = lambda {|h,k| h[k] = Hash.new(&hproc) }
hash = Hash.new(&hproc)

So if I try to increment on a key that isn’t there yet, like this:

hash[1][2][3] += 1

I get an undefined method for hash error.

Am I using it correctly if I want the inner hash to have a default?
Please forgive me if I have missed something in your example. I am
still relatively new to Ruby and am trying to get to the next level by
writing more sophisticated programs.

Thanks,

Glenn


From: David A. Black removed_email_address@domain.invalid
To: ruby-talk ML removed_email_address@domain.invalid
Sent: Sunday, January 4, 2009 4:22:37 PM
Subject: Re: Default values of hashes

Hi –

On Mon, 5 Jan 2009, Sebastian H. wrote:

else

h = nested_hash(3, :foo)

Same as h = Hash.new {|h,k| h[k] = Hash.new {|h,k| h[k] = Hash.new(:foo)}}

Why not make it just keep going automatically? (See my other post.)

David


David A. Black / Ruby Power and Light, LLC
Ruby/Rails consulting & training: http://www.rubypal.com
Coming in 2009: The Well-Grounded Rubyist (http://manning.com/black2)

http://www.wishsight.com => Independent, social wishlist management!


#10

David A. Black wrote:

Why not make it just keep going automatically? (See my other post.)

Because a) that way you can’t define a default value or proc for the
innermost
hash, b) you won’t get an error if you accidentally go one nesting too
deep,
c) because that’s what the op asked for and most importantly d) because
you
already gave the answer with unlimited nesting :wink:


#11

Glenn,

A Hash can either have a default value:
h1 = Hash.new(:foo)
h1[‘new’]
=> :foo
or a default proc
h2 = Hash.new { :foo }
h2[‘new’]
=> :foo
but the proc doesn’t create the entry in the hash
h2.keys
=> []
See my first response or the rdoc for Hash.new

If you want to create the entry, you need to use the hash and key
values passed to the block:
h3 = Hash.new {|h,k| h[k] = :foo }
h3[‘new’]
=> :foo
h3.keys
=> [“new”]
h3
=> {“new”=>:foo}

You’re trying to eat your cake and have it, too. An initialization
similar to your recent post is needed.

def deep_hash(levels=1, default=nil)
if levels == 0
if default
Hash.new { |h,k| h[k] = default }
else
Hash.new
end
else
Hash.new { |h,k| h[k] = deep_hash(levels-1, default) }
end
end

if File.expand_path($0) == File.expand_path(FILE)
require ‘test/unit’

class DeepHashTest < Test::Unit::TestCase
def test_zero_levels_and_no_default_is_normal_hash
expected = Hash.new
actual = deep_hash(0)
assert_equal expected, actual
assert_nil actual[:key]
assert_equal [], actual.keys
end

 def test_zero_levels_and_default_is_normal_hash
   expected = Hash.new(42)
   actual = deep_hash(0,42)
   assert_equal expected, actual
   assert_equal 42, actual[:key]
   assert_equal [:key], actual.keys
 end

 def test_one_level_and_no_default_is_simple_nested_hash
   expected = Hash.new {|h,k| h[k] = Hash.new}
   actual = deep_hash(1)
   assert_equal expected, actual
   assert_nil actual[1][:key]
   assert_equal [1], actual.keys
   assert_equal [], actual[1].keys
 end

 def test_one_level_and_default_is_simple_nested_hash
   expected = Hash.new {|h,k| h[k] = Hash.new(43)}
   actual = deep_hash(1,43)
   assert_equal expected, actual
   assert_equal 43, actual[1][:key]
   assert_equal [1], actual.keys
   assert_equal [:key], actual[1].keys
 end

 def test_three_levels_and_default_for_glenn
   expected = Hash.new {|h3,k3| h3[k3] = Hash.new {|h2,k2| h2[k2]

= Hash.new {|h,k| h[k] = Hash.new(44) } } }
actual = deep_hash(3, 44)
assert_equal expected, actual
assert(expected == actual, “Using ==”)
assert_equal 44, actual[:a][:b][:c][:d]
actual[:a][:b][:c][:new] += 1
assert_equal 45, actual[:a][:b][:c][:new]
assert_equal [:d,:new], actual[:a][:b][:c].keys
end
end
end

END

The meaning of a certain number of levels is perhaps off-by-one
relative to what you want and I’m not trying to allow the inner-most
(deepest) hash to have a default_proc since it would need to have
something like lambda {|h,k|h[k]=block.call}.

I suppose that can be left as an exercise for the reader. :wink:

-Rob

Rob B. http://agileconsultingllc.com
removed_email_address@domain.invalid


#12

Hi –

On Mon, 5 Jan 2009, Sebastian H. wrote:

David A. Black wrote:

Why not make it just keep going automatically? (See my other post.)

Because a) that way you can’t define a default value or proc for the innermost
hash, b) you won’t get an error if you accidentally go one nesting too deep,
c) because that’s what the op asked for and most importantly d) because you
already gave the answer with unlimited nesting :wink:

I’m with you on a) and d) :slight_smile: I didn’t understand the OP to be asking
for that constraint. As for a), you could just do the old-fashioned:

hash[1][2][3] = Hash.new(0)

and at least reap the benefits of getting the intermediate ones.

David


David A. Black / Ruby Power and Light, LLC
Ruby/Rails consulting & training: http://www.rubypal.com
Coming in 2009: The Well-Grounded Rubyist (http://manning.com/black2)

http://www.wishsight.com => Independent, social wishlist management!


#13

Thanks for the tips. I learned a lot from all of the suggestions.

Not sure, though, if I understand this line:

hash[1][2][3] = Hash.new(0)

Is that a valid line on its own. If so, it does not seem to work for
me. I get the following error:

undefined method `[]=’ for 0

Glenn


From: David A. Black removed_email_address@domain.invalid
To: ruby-talk ML removed_email_address@domain.invalid
Sent: Monday, January 5, 2009 7:12:59 AM
Subject: Re: Default values of hashes

Hi –

On Mon, 5 Jan 2009, Sebastian H. wrote:

David A. Black wrote:

Why not make it just keep going automatically? (See my other post.)

Because a) that way you can’t define a default value or proc for the innermost
hash, b) you won’t get an error if you accidentally go one nesting too deep,
c) because that’s what the op asked for and most importantly d) because you
already gave the answer with unlimited nesting :wink:

I’m with you on a) and d) :slight_smile: I didn’t understand the OP to be asking
for that constraint. As for a), you could just do the old-fashioned:

hash[1][2][3] = Hash.new(0)

and at least reap the benefits of getting the intermediate ones.

David

– David A. Black / Ruby Power and Light, LLC
Ruby/Rails consulting & training: http://www.rubypal.com
Coming in 2009: The Well-Grounded Rubyist (http://manning.com/black2)

http://www.wishsight.com => Independent, social wishlist management!


#14

On Sun, Jan 4, 2009 at 2:37 PM, David A. Black removed_email_address@domain.invalid
wrote:

Hi –
It’s come up before; here’s the solution I seem to recall:

hproc = lambda {|h,k| h[k] = Hash.new(&hproc) }
hash = Hash.new(&hproc)

hash[1][2][3] = 4
p hash # {1=>{2=>{3=>4}}}

Hi,

Another version, without the intermediate lambda:

irb(main):009:0> hash = Hash.new{|h,k| h[k] = Hash.new(&h.default_proc)}
=> {}
irb(main):010:0> hash[1][2][3] = 4
=> 4
irb(main):011:0> hash
=> {1=>{2=>{3=>4}}}

Jesus.


#15

Thanks, David. I think I understand it now.


From: David A. Black removed_email_address@domain.invalid
To: ruby-talk ML removed_email_address@domain.invalid
Sent: Saturday, January 10, 2009 9:53:49 AM
Subject: Re: Default values of hashes

Hi –

On Wed, 7 Jan 2009, Glenn wrote:

Thanks for the tips. I learned a lot from all of the suggestions.

Not sure, though, if I understand this line:

hash[1][2][3] = Hash.new(0)

Is that a valid line on its own. If so, it does not seem to work for me. I get the following error:

undefined method `[]=’ for 0

Here’s how to use it:

hproc = lambda {|h,k| h[k] = Hash.new(&hproc) }
=> #Proc:0x00248420@:14(irb)

hash = Hash.new(&hproc)
=> {}

hash[1][2][3] = Hash.new(0)
=> {}

hash[1][2][3][“x”] += 10
=> 10

hash[1][2][3]
=> {“x”=>10}

David


David A. Black / Ruby Power and Light, LLC
Ruby/Rails consulting & training: http://www.rubypal.com
Coming in 2009: The Well-Grounded Rubyist (http://manning.com/black2)

http://www.wishsight.com => Independent, social wishlist management!


#16

Hi –

On Wed, 7 Jan 2009, Glenn wrote:

Thanks for the tips. I learned a lot from all of the suggestions.

Not sure, though, if I understand this line:

hash[1][2][3] = Hash.new(0)

Is that a valid line on its own. If so, it does not seem to work for me. I get the following error:

undefined method `[]=’ for 0

Here’s how to use it:

hproc = lambda {|h,k| h[k] = Hash.new(&hproc) }
=> #Proc:0x00248420@:14(irb)

hash = Hash.new(&hproc)
=> {}

hash[1][2][3] = Hash.new(0)
=> {}

hash[1][2][3][“x”] += 10
=> 10

hash[1][2][3]
=> {“x”=>10}

David


David A. Black / Ruby Power and Light, LLC
Ruby/Rails consulting & training: http://www.rubypal.com
Coming in 2009: The Well-Grounded Rubyist (http://manning.com/black2)

http://www.wishsight.com => Independent, social wishlist management!