[lnkForumImage]
TotalShareware - Download Free Software

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


 

Forums >

comp.lang.ruby

Default values of hashes

Glenn

1/3/2009 4:15:00 AM

[Note: parts of this message were removed to make it a legal post.]

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
14 Answers

Rob Biedenharn

1/3/2009 5:00:00 AM

0

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://agileconsult...
Rob@AgileConsultingLLC.com

On Jan 2, 2009, at 11:15 PM, Glenn wrote:

> 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




Peña, Botp

1/3/2009 5:04:00 AM

0

From: Glenn [mailto:glenn_ritz@yahoo.com]=20
# I would be able to add any key to trt_hash and its value=20
# would have a default of an empty hash, and then I'd be able=20
# to add any element I wanted to the nested hash, and its=20
# default would be 0. But this does not seem to work.
# ...I thought if I defined trt_hash like this:
# trt_hash =3D 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/M0...

try eg,

> trt_hash =3D Hash.new{|h,k| h[k]=3D Hash.new{|h2,k2| h2[k2]=3D0} }
=3D> {}
> trt_hash["a"]
=3D> {}
> trt_hash
=3D> {"a"=3D>{}}
> trt_hash["a"][:x]
=3D> 0
> trt_hash
=3D> {"a"=3D>{:x=3D>0}}
> trt_hash["a"][:x]+=3D100
=3D> 100
> trt_hash
=3D> {"a"=3D>{:x=3D>100}}
> trt_hash["a"][:x]+=3D100
=3D> 200
> trt_hash
=3D> {"a"=3D>{:x=3D>200}}
> trt_hash["a"][:w]+=3D100
=3D> 100
> trt_hash
=3D> {"a"=3D>{:w=3D>100, :x=3D>200}}
> trt_hash["b"][:w]+=3D100
=3D> 100
> trt_hash
=3D> {"a"=3D>{:w=3D>100, :x=3D>200}, "b"=3D>{:w=3D>100}}

Glenn

1/4/2009 2:30:00 AM

0

Both solutions are great. I had something else that was sort of working, b=
ut these are far more elegant.=0A=0AThanks!=0A=0A=0A=0A____________________=
____________=0AFrom: "Pe=F1a, Botp" <botp@delmonte-phil.com>=0ATo: ruby-tal=
k ML <ruby-talk@ruby-lang.org>=0ASent: Saturday, January 3, 2009 12:03:35 A=
M=0ASubject: Re: Default values of hashes=0A=0AFrom: Glenn [mailto:glenn_ri=
tz@yahoo.com] =0A# I would be able to add any key to trt_hash and its value=
=0A# would have a default of an empty hash, and then I'd be able =0A# to a=
dd any element I wanted to the nested hash, and its =0A# default would be 0=
But this does not seem to work.=0A# ...I thought if I defined trt_hash l=
ike this:=0A# trt_hash =3D Hash.new( Hash.new(0))=0A=0Athat is near to you=
r 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...
Hash.src/M002868.html=0A=0Atry eg,=0A=0A> trt_hash =3D Hash.new{|h,k| h[k]=
=3D Hash.new{|h2,k2| h2[k2]=3D0} }=0A=3D> {}=0A> trt_hash["a"]=0A=3D> {}=0A=
> trt_hash=0A=3D> {"a"=3D>{}}=0A> trt_hash["a"][:x]=0A=3D> 0=0A> trt_hash=
=0A=3D> {"a"=3D>{:x=3D>0}}=0A> trt_hash["a"][:x]+=3D100=0A=3D> 100=0A> trt_=
hash=0A=3D> {"a"=3D>{:x=3D>100}}=0A> trt_hash["a"][:x]+=3D100=0A=3D> 200=0A=
> trt_hash=0A=3D> {"a"=3D>{:x=3D>200}}=0A> trt_hash["a"][:w]+=3D100=0A=3D> =
100=0A> trt_hash=0A=3D> {"a"=3D>{:w=3D>100, :x=3D>200}}=0A> trt_hash["b"][:=
w]+=3D100=0A=3D> 100=0A> trt_hash=0A=3D> {"a"=3D>{:w=3D>100, :x=3D>200}, "b=
"=3D>{:w=3D>100}}

Glenn

1/4/2009 1:06:00 PM

0

I actually have a followup question to this one.=0A=0AIt turns out that som=
etimes I will need to go more than two levels deep with the hashes. I infe=
rred that to go three levels deep, the code would be:=0A=0Ah =3D Hash.new{|=
h,k| h[k]=3D Hash.new{|h2,k2| h2[k2]=3D Hash.new{|h3,k3| h3[k3]=3D0}}}=0A=
=0AThis 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 withi=
n a method. In other words, if I need 3 levels, it creates a hash within h=
ash 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?=0A=0AThanks,=0A=0AGlenn=0A=0A=
=0A=0A________________________________=0AFrom: Glenn <glenn_ritz@yahoo.com>=
=0ATo: ruby-talk ML <ruby-talk@ruby-lang.org>=0ASent: Saturday, January 3, =
2009 9:29:37 PM=0ASubject: Re: Default values of hashes=0A=0ABoth solutions=
are great. I had something else that was sort of working, but these are f=
ar more elegant.=0A=0AThanks!=0A=0A=0A=0A________________________________=
=0AFrom: "Pe=F1a, Botp" <botp@delmonte-phil.com>=0ATo: ruby-talk ML <ruby-t=
alk@ruby-lang.org>=0ASent: Saturday, January 3, 2009 12:03:35 AM=0ASubject:=
Re: Default values of hashes=0A=0AFrom: Glenn [mailto:glenn_ritz@yahoo.com=
] =0A# I would be able to add any key to trt_hash and its value =0A# would =
have a default of an empty hash, and then I'd be able =0A# to add any eleme=
nt I wanted to the nested hash, and its =0A# default would be 0. But this =
does not seem to work.=0A# ...I thought if I defined trt_hash like this:=0A=
# trt_hash =3D Hash.new( Hash.new(0))=0A=0Athat is near to your objective.=
you just have to build the hash since defaults do not, ie use the block fo=
rm to store value in hash http://www.ruby-doc.org/core/classes/Has...
2868.html=0A=0Atry eg,=0A=0A> trt_hash =3D Hash.new{|h,k| h[k]=3D Hash.new{=
|h2,k2| h2[k2]=3D0} }=0A=3D> {}=0A> trt_hash["a"]=0A=3D> {}=0A> trt_hash=0A=
=3D> {"a"=3D>{}}=0A> trt_hash["a"][:x]=0A=3D> 0=0A> trt_hash=0A=3D> {"a"=3D=
>{:x=3D>0}}=0A> trt_hash["a"][:x]+=3D100=0A=3D> 100=0A> trt_hash=0A=3D> {"a=
"=3D>{:x=3D>100}}=0A> trt_hash["a"][:x]+=3D100=0A=3D> 200=0A> trt_hash=0A=
=3D> {"a"=3D>{:x=3D>200}}=0A> trt_hash["a"][:w]+=3D100=0A=3D> 100=0A> trt_h=
ash=0A=3D> {"a"=3D>{:w=3D>100, :x=3D>200}}=0A> trt_hash["b"][:w]+=3D100=0A=
=3D> 100=0A> trt_hash=0A=3D> {"a"=3D>{:w=3D>100, :x=3D>200}, "b"=3D>{:w=3D>=
100}}

David A. Black

1/4/2009 1:37:00 PM

0

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.r...
Coming in 2009: The Well-Grounded Rubyist (http://manning....)

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

Sebastian Hungerecker

1/4/2009 3:01:00 PM

0

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. =A0I do not know how to do this (or even if it is
> possible). =A0Any suggestions on this one?

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

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

Usage:
h =3D nested_hash(1)
# Same as h =3D Hash.new

h =3D nested_hash(1) {|h,k| h[k] =3D Array.new}
# Same as h =3D Hash.new {|h,k| h[k] =3D Array.new}

h =3D nested_hash(3)
# Same as h =3D Hash.new {|h,k| h[k] =3D Hash.new {|h,k| h[k] =3D Hash.new}}

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

HTH,
Sebastian
=2D-=20
Jabber: sepp2k@jabber.org
ICQ: 205544826

Glenn

1/5/2009 1:47:00 AM

0

[Note: parts of this message were removed to make it a legal post.]

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:

> 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)}}

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.r...
Coming in 2009: The Well-Grounded Rubyist (http://manning....)

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

Rob Biedenharn

1/5/2009 4:48:00 AM

0

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://agileconsult...
Rob@AgileConsultingLLC.com

On Jan 4, 2009, at 8:47 PM, Glenn wrote:

> 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:
>
>> 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)}}
>
> 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.r...
> Coming in 2009: The Well-Grounded Rubyist (http://manning....)
>
> http://www.wis... => Independent, social wishlist management!




Sebastian Hungerecker

1/5/2009 7:44:00 AM

0

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 ;-)

--
Jabber: sepp2k@jabber.org
ICQ: 205544826

David A. Black

1/5/2009 12:13:00 PM

0

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.r...
Coming in 2009: The Well-Grounded Rubyist (http://manning....)

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