Ruby Koans: Fun With Hashes

So, I was working through getting Enlightened about hashes, when I came across this:

 79   def test_default_value
 80     hash1 = Hash.new
 81     hash1[:one] = 1
 82 
 83     assert_equal 1, hash1[:one]
 84     assert_equal nil, hash1[:two]
 85 
 86     hash2 = Hash.new("dos")
 87     hash2[:one] = 1
 88 
 89     assert_equal 1, hash2[:one]
 90     assert_equal "dos", hash2[:two]
 91   end
 92 
 93   def test_default_value_is_the_same_object
 94     hash = Hash.new([])
 95 
 96     hash[:one] << "uno"
 97     hash[:two] << "dos"
 98 
 99     assert_equal ["uno","dos"], hash[:one]
100     assert_equal ["uno","dos"], hash[:two]
101     assert_equal ["uno","dos"], hash[:three]
102 
103     assert_equal true, hash[:one].object_id == hash[:two].object_id
104   end
105 
106   def test_default_value_with_block
107     hash = Hash.new {|hashkey| hash[key] = [] }
108 
109     hash[:one] << "uno"
110     hash[:two] << "dos"
111 
112     assert_equal ["uno"], hash[:one]
113     assert_equal ["dos"], hash[:two]
114     assert_equal [], hash[:three]
115   end

It's easy to cheat and get these to pass, but I want to know what is going on, specifically in lines: 86, 90, 94, 96/97, 109/110, and 114.

Time for some research!

So, doing this, hash = Hash.new("dos"), means that "dos" will be used for all default values in that hash.

That explains line 90 and why the correct value is "dos", rather than nil, like I originally thought.  Turns out if we don't provide a default value, nil is what you'll get.  Makes sense, I guess.

So then on line 94, we are setting the default value to an empty array.  What I forgot before was that << is pushing a value onto an array.  In this case, the empty arrays that are the values associated with the keys: :one and :two.  Cool, it makes sense!

This brings us to line 114.  It is related to how this hash was created on line 107.  In this case, it was created with a block.  This is defaulting the value to an empty array each time, but would pay attention to the value of the key if you pass one in.

The docs are good and illustrate the difference:

h = Hash.new("Go Fish")
h["a"] = 100
h["b"] = 200
h["a"]           #=> 100
h["c"]           #=> "Go Fish"
# The following alters the single default object
h["c"].upcase!   #=> "GO FISH"
h["d"]           #=> "GO FISH"
h.keys           #=> ["a", "b"]

# While this creates a new default object each time
h = Hash.new { |hash, key| hash[key] = "Go Fish: #{key}" }
h["c"]           #=> "Go Fish: c"
h["c"].upcase!   #=> "GO FISH: C"
h["d"]           #=> "Go Fish: d"
h.keys           #=> ["c", "d"]

Wow, I think I just learned some things here, but the trick will be to remember them.

Comments

Popular posts from this blog

Next Up

Becoming a Rubyist