Problem appending to arrays in a hash of arrays


#1

I’m trying to create a hash with each element being an array (initially
empty), and I’ve tried a few variations to get what I want. Why don’t
the
last 2 lines work?

h = Hash.new ( Array.new )
h[“abc”] = h[“abc”] + [ “foo” ] # this works
h[“abc”] << “foo” # this doesn’t
h[“abc”].push ( “foo” ) # this doesn’t either

(Note: the above code isn’t the actual code I’m using, but a simplified
version.)

Mike S.


#2

On 5/22/07, Mike S. removed_email_address@domain.invalid wrote:

version.)

Mike S.

g = Hash.new {|h,k| h[k]=[]}
=> {}

g[“abc”] << :foo
=> [:foo]

g
=> {“abc”=>[:foo]}


#3

I sort of understand your explanation, but what are :a, :b, etc.?

Thanks,
Mike S.


#4

On 5/22/07, Chris C. removed_email_address@domain.invalid wrote:

(Note: the above code isn’t the actual code I’m using, but a simplified
version.)

g = Hash.new {|h,k| h[k]=[]}
=> {}

g[“abc”] << :foo
=> [:foo]

g
=> {“abc”=>[:foo]}

That’s a fish for Mike, here’s a little fishing lesson.

Mike, you didn’t say how it didn’t work, there are actually two
related problems.

The Hash creation technique:

Hash.new(expression)

creates a hash which returns the value of expression as the default
value when a key doesn’t exist in the hash. Normally hashes return
nil for an unknown key.

The expression is evaluated before calling the new method, so in your
case

h = Hash.new(Array.new)

creates an empty array for the default value, this instance of array
will be returned for any key which isn’t in the hash. Now:

1: h[:a] += [:c] # this replaces h[:a]
2: h[:b] << :d # this appends :d to what is returned by h[:b]
3: h[:b] # returns [:d] which looks like it worked BUT
4: h[:c] # also returns [:d] ???

What happened is that the hash simply returns that default value,
which is the same object every time. That’s problem #1, line 2
actually changes the state of the default value. Now:

5: h[:d] << :f # returns [:d, :f] and so does
6: h[:b] # ??? and
7: h[:c] # ???

This is problem two, accessing the hash via a non-existent key doesn’t
change the hash, it simply returns the default value.

  1. h.inspect # returns “{:a=>[:c]}”

Now Chris’s solution uses Hash.new without a parameter but with a block

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

which makes a hash which, if accessed with an unknown key, evaluates
the block passing the hash itself, and the key as parameters, and
returns the value of the block. Chris’s block actually stores an
empty array in the hash at that key. And since the block is evaluated
at the time the hash is accessed rather than when it is created, each
key will get it’s own instance of an empty array. Which solves both
problems.

Note that the block given to Hash.new can do whatever it wants to with
the key and hash. Chris’s block stored the value, which is generally
done, but it doesn’t have to. For example if the Hash were created
with

 h = Hash.new {[]}

we’d get a hash which returned a newly instantiated empty array for an
unknown key, but didn’t store it. This wouldn’t work for what Mike’s
trying to do:

h[:a] << :b  # returns [:b]  but then
h[:a]             # returns []

This solves problem #1, but not problem #2. This technique might be
useful in other situations though.


Rick DeNatale

My blog on Ruby
http://talklikeaduck.denhaven2.com/


#5

On 5/23/07, Mike S. removed_email_address@domain.invalid wrote:

I sort of understand your explanation, but what are :a, :b, etc.?

They’re just symbols, strings or most other objects would work just as
well as keys, values could be anything.


Rick DeNatale

My blog on Ruby
http://talklikeaduck.denhaven2.com/