-2

In terms of Hash default values, what happens in this instance when you pass a block as a default value, such as:

hash = Hash.new {|hash, key| hash[key] = [] } 

I ran the following code:

hash[:one] << "uno"
hash[:two] << "dos"  
hash[:three] 
hash[:four]

hash #returns the hash 

and the output is:

{:two=>["dos"], :three=>[], :four=>[], :one=>["uno"]} 

Can someone please explains what happens in simple English?

Robert Harvey
  • 178,213
  • 47
  • 333
  • 501
NdaJunior
  • 374
  • 3
  • 17

1 Answers1

2

The objective

Let's start here:

h = {}
h[:a] = [] unless h.key?(:a)
h[:a] << 7

We create an empty hash h. Each value of this hash will be an array of values. We wish to append 7 to the array that is the value of key :a. If h already has a key :a, we have something like h[:a] #=> [3,5], so we just append 7 to that array:

h[:a] << 7 #=> [3,5,7]

If, however, h does not have a key :a (so h[:a] #=> nil), we first need to set h[:a] to an empty array. Hence the need for:

h[:a] = [] unless h.key?(:a)

If h cannot already have a key :a with value nil (in other words, if no values are intentionally nil), we could instead write:

h[:a] = [] unless h[:a]

Ruby-like trick

We can make this more Ruby-like in a couple of ways. The first is:

h = {}
(h[:a] ||= []) << 7 #=> [7] 
h #=> {:a=>[7]} 
(h[:a] ||= []) << 9 #=> [7, 9] 
h #=> {:a=>[7, 9]}

When Ruby sees:

h[:a] ||= []

the first thing she does it convert it to:

h[:a] = h[:a] || []

The steps are therefore as follows:

(h[:a] = h[:a] || []) << 7
  #=> (h[:a] = nil || []) << 7
  #=> (h[:a] = []) << 7
  #=> h[:a] << 7
h[:a] #=> [7]    
(h[:a] = h[:a] || []) << 9
  #=> (h[:a] = [7] || []) << 9
  #=> (h[:a] = [7]) << 9
  #=> h[:a] << 9
h[:a] #=> [7,9]

Ruby-like default value for hash

The second more Ruby-like way is equivalent to the first, but implemented by defining a default value for the hash. If a hash h does not have a key k, h[k] returns the default value. Does this also change the hash? Let's put off the question for the moment.

The docs for Hash::new explain that there are two ways to define a default value.

Wrong default value

The first, for making the default value an empty array, is:

h = Hash.new([]) #=> {}

so if we now write:

h[:a] #=> []

this returns the default value ([]). It does not alter the hash:

h #=> {}

We may be tempted to write:

h[:a] << 7
  #=> [] << 7 => [7]

(h[:a] returns the default value because there is no key :a.) As you see, this does not alter the hash, it just returns an array [7] which is not attached to anything, so is garbage-collected.

Now let's try this:

(h[:a] = h[:a]) << 7
  #=> (h[:a] = []) << 7 => [7]
h #=> {:a=>[7]} 
(h[:a] = h[:a]) << 9
  #=> (h[:a] = [7]) << 9 => [7, 9] 
h #=> {:a=>[7, 9]} 

So far, so good, but there's a problem:

(h[:b] = h[:b]) << 11 #=> [7, 9, 11] 
h #=> {:a=>[7, 9, 11], :b=>[7, 9, 11]} 
(h[:b] = h[:b]) << 13 #=> [7, 9, 11, 13] 
h #=> {:a=>[7, 9, 11, 13], :b=>[7, 9, 11, 13]} 

As you see, there is just one default empty array.

Right default value

Fortunately, there is another way to specify a default array: by giving Hash#new a block. (We've finally reached the point of the question.)

If the hash h does not have a key k, h[k] invokes the block. The block has two block variables, h and k. You can do whatever you want in the block. For example:

@a = 3
h = Hash.new { |h,k| @a = 7 }
h[:a] #=> 7
h     #=> {}
@a    #=> 7

Now I doubt very much that you'd want to do this, but you could. For the present problem you want the value of a new key to be an empty array:

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

(Be careful with this. If we executed:

puts h[:a]
  #=> []

we'd then have

h #=> { :a=>[] }

)

So:

h[:a] << 7

first invokes the block, because h does not have a key k, so we now have:

{ :a=>[] }[:a] << 7
h #=> { :a=>[7] }

Now when we execute:

h[:a] << 9
h #=> {:a=>[7, 9]}   

the block is not called, because h has a key :a.

That's about it. Any questions?

Cary Swoveland
  • 106,649
  • 6
  • 63
  • 100
  • Wow this was a really comprehensive explanation! You really went beyond the call of duty for me and I thoroughly appreciate that! I am forever indebted to you! Thanks once again! It all makes sense now! Lightbulb moment!!!!! – NdaJunior Jun 25 '15 at 17:40
  • I am glad to help. Lightbulb moments are great, eh? – Cary Swoveland Jun 25 '15 at 18:12