I am new to Ruby and running Ruby Koans. In Ruby Koans, in about_hashes.rb file, there is an example of assigning default value to a hash.
hash = Hash.new([])
hash[:one] << "uno"
hash[:two] << "dos"
puts hash[:one] # this is ["uno", "dos"]
here both hash[:one] & hash[:two] or any key like hash[:three] (non existing key) all have the value ["uno", and "dos"] I did not understand how "<<" is used here. Also, when I tried extracting keys & values of the hash, or print the keys/values, it is empty.
puts (hash.values.size) # size is 0 here
puts (hash.keys.size) # size is 0
puts hash.values # nothing gets printed
puts hash.keys #nothing gets printed.
So what is happening here? Where are the values getting stored, if they are not getting stored in the hash as keys or values.
in the next example, when Hash is defined as
hash = Hash.new {|hash, key| hash[key] = [] }
hash[:one] << "uno"
hash[:two] << "dos"
puts hash[:one] #this is "uno"
puts hash[:two] #this is "dos"
puts hash[:three] # this is undefined.
I guess in the second example, hash is initializing all the keys with a blank array. So "uno" is getting appended to the empty array when "<<" this operator is used? I am confused about both the examples. I don't know what is happening in both of them. I couldn't find much information on this example in google as well. If somebody can help me explain these 2 examples it will be helpful. Thanks in advance
hash = Hash.new([])
This statement creates an empty hash that has a default value of an empty array. If hash does not have a key k, hash[k] returns the default value, []. This is important: simply returning the default value does not modify the hash.
When you write:
hash[:one] << "uno" #=> ["uno"]
before hash has a key :one, hash[:one] is replaced by the default value, so we have:
[] << "uno" #=> ["uno"]
which explains why hash is not changed:
hash #=> {}
Now write:
hash[:two] << "dos" #=> ["uno", "dos"]
hash #=> {}
To see why we get this result, let's re-write the above as follows:
hash = Hash.new([]) #=> {}
hash.default #=> []
hash.default.object_id #=> 70209931469620
a = (hash[:one] << "uno") #=> ["uno"]
a.object_id #=> 70209931469620
hash.default #=> ["uno"]
b = (hash[:two] << "dos") #=> ["uno", "dos"]
b.object_id #=> 70209931469620
hash.default #=> ["uno", "dos"]
So you see that the default array whose object_id is 70209931469620 is the single array to which "uno" and "dos" are appended.
If we had instead written:
hash[:one] = hash[:one] << "uno"
#=> hash[:one] = [] << "uno" => ["uno"]
hash #=> { :one=>"uno" }
we get what we were hoping for, but not so fast:
hash[:two] = hash[:two] << "dos"
#=> ["uno", "dos"]
hash
#=> {:one=>["uno", "dos"], :two=>["uno", "dos"]}
which is still not what we want because both keys have values that are the same array.
hash = Hash.new {|hash, key| hash[key] = [] }
This statements causes the block to be executed when hash does not have a key key. This does change the hash, by adding a key value pair1.
So now:
hash[:one] << "uno"
causes the block:
{ |h,k| h[k] = [] }
to make the assignment:
hash[:one] = []
after which:
hash[:one] << "uno"
appends "uno" to an empty array that is the value for the key :one, which we can verify:
hash #=> { :one=>"uno" }
This has the same effect as writing:
hash[:one] = (hash[:one] || []) << "uno"
(the expanded version of (hash[:one] ||= []) << "uno") when there is no default value.
Similarly,
hash[:two] << "dos" #=> ["dos"]
hash #=> {:one=>["uno"], :two=>["dos"]}
which is usually the result we want.
1 The block need not change the hash, however. The block can contain any code, including, for example, { puts "Have a nice day" }.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With