Forum: Ruby Default values of hashes

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
5f1ad1dbc8cfc1c40988a6b6bec9c5a2?d=identicon&s=25 Glenn (Guest)
on 2009-01-03 05:16
(Received via mailing list)
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
Ef3aa7f7e577ea8cd620462724ddf73b?d=identicon&s=25 Rob Biedenharn (Guest)
on 2009-01-03 06:01
(Received via mailing list)
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 Biedenharn    http://agileconsultingllc.com
Rob@AgileConsultingLLC.com
6087a044557d6b59ab52e7dd20f94da8?d=identicon&s=25 Peña, Botp (Guest)
on 2009-01-03 06:04
(Received via mailing list)
From: Glenn [mailto:glenn_ritz@yahoo.com]
# 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}}
5f1ad1dbc8cfc1c40988a6b6bec9c5a2?d=identicon&s=25 Glenn (Guest)
on 2009-01-04 03:31
(Received via mailing list)
Both solutions are great.  I had something else that was sort of
working, but these are far more elegant.

Thanks!



________________________________
From: "Peña, Botp" <botp@delmonte-phil.com>
To: ruby-talk ML <ruby-talk@ruby-lang.org>
Sent: Saturday, January 3, 2009 12:03:35 AM
Subject: Re: Default values of hashes

From: Glenn [mailto:glenn_ritz@yahoo.com]
# 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}}
5f1ad1dbc8cfc1c40988a6b6bec9c5a2?d=identicon&s=25 Glenn (Guest)
on 2009-01-04 14:07
(Received via mailing list)
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 <glenn_ritz@yahoo.com>
To: ruby-talk ML <ruby-talk@ruby-lang.org>
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" <botp@delmonte-phil.com>
To: ruby-talk ML <ruby-talk@ruby-lang.org>
Sent: Saturday, January 3, 2009 12:03:35 AM
Subject: Re: Default values of hashes

From: Glenn [mailto:glenn_ritz@yahoo.com]
# 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}}
F53b05cdbdf561cfe141f69b421244f3?d=identicon&s=25 David A. Black (Guest)
on 2009-01-04 14:38
(Received via mailing list)
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!
7a561ec0875fcbbe3066ea8fe288ec77?d=identicon&s=25 Sebastian Hungerecker (Guest)
on 2009-01-04 16:02
(Received via mailing list)
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
F53b05cdbdf561cfe141f69b421244f3?d=identicon&s=25 David A. Black (Guest)
on 2009-01-04 22:24
(Received via mailing list)
Hi --

On Mon, 5 Jan 2009, Sebastian Hungerecker 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!
5f1ad1dbc8cfc1c40988a6b6bec9c5a2?d=identicon&s=25 Glenn (Guest)
on 2009-01-05 02:49
(Received via mailing list)
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 <dblack@rubypal.com>
To: ruby-talk ML <ruby-talk@ruby-lang.org>
Sent: Sunday, January 4, 2009 4:22:37 PM
Subject: Re: Default values of hashes

Hi --

On Mon, 5 Jan 2009, Sebastian Hungerecker 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!
Ef3aa7f7e577ea8cd620462724ddf73b?d=identicon&s=25 Rob Biedenharn (Guest)
on 2009-01-05 05:50
(Received via mailing list)
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. ;-)

-Rob

Rob Biedenharn    http://agileconsultingllc.com
Rob@AgileConsultingLLC.com
7a561ec0875fcbbe3066ea8fe288ec77?d=identicon&s=25 Sebastian Hungerecker (Guest)
on 2009-01-05 08:45
(Received via mailing list)
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 ;-)
F53b05cdbdf561cfe141f69b421244f3?d=identicon&s=25 David A. Black (Guest)
on 2009-01-05 13:13
(Received via mailing list)
Hi --

On Mon, 5 Jan 2009, Sebastian Hungerecker 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 ;-)

I'm with you on a) and d) :-) 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!
5f1ad1dbc8cfc1c40988a6b6bec9c5a2?d=identicon&s=25 Glenn (Guest)
on 2009-01-06 20:45
(Received via mailing list)
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 <dblack@rubypal.com>
To: ruby-talk ML <ruby-talk@ruby-lang.org>
Sent: Monday, January 5, 2009 7:12:59 AM
Subject: Re: Default values of hashes

Hi --

On Mon, 5 Jan 2009, Sebastian Hungerecker 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 ;-)

I'm with you on a) and d) :-) 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!
E088bb5c80fd3c4fd02c2020cdacbaf0?d=identicon&s=25 Jesús Gabriel y Galán (Guest)
on 2009-01-07 16:45
(Received via mailing list)
On Sun, Jan 4, 2009 at 2:37 PM, David A. Black <dblack@rubypal.com>
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.
F53b05cdbdf561cfe141f69b421244f3?d=identicon&s=25 David A. Black (Guest)
on 2009-01-10 15:56
(Received via mailing list)
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@(irb):14>
>> 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!
5f1ad1dbc8cfc1c40988a6b6bec9c5a2?d=identicon&s=25 Glenn (Guest)
on 2009-01-10 16:35
(Received via mailing list)
Thanks, David.  I think I understand it now.





________________________________
From: David A. Black <dblack@rubypal.com>
To: ruby-talk ML <ruby-talk@ruby-lang.org>
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@(irb):14>
>> 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!
This topic is locked and can not be replied to.