I was going through Ruby Koans tutorial series, when I came upon this in about_hashes.rb
:
def test_default_value_is_the_same_object hash = Hash.new([]) hash[:one] << "uno" hash[:two] << "dos" assert_equal ["uno", "dos"], hash[:one] assert_equal ["uno", "dos"], hash[:two] assert_equal ["uno", "dos"], hash[:three] assert_equal true, hash[:one].object_id == hash[:two].object_id end
The values in assert_equals
, is actually what the tutorial expected. But I couldn't understand how there is a difference between using <<
operator and =
operator?
My expectation was that:
hash[:one]
would be ["uno"]
hash[:two]
would be ["dos"]
hash[:three]
would be []
Can someone please explain why my expectation was wrong?
Commonly referred to as the “shovel operator”, << is a method in Ruby that is commonly used to push an object onto an array, but you can shovel into strings as well.
A Hash is a collection of key-value pairs like this: "employee" = > "salary". It is similar to an Array, except that indexing is done via arbitrary keys of any object type, not an integer index.
Hashes are inherently unordered. Hashes provide amortized O(1) insertion and retrieval of elements by key, and that's it. If you need an ordered set of pairs, use an array of arrays.
A hash table is a data structure optimized for random reads where entries are stored as key-value pairs into an internal array. Since we access elements by hashing their key to a particular position, (assuming uniform hashing) searches run in constant time.
You have mixed up the way this works a bit. First off, a Hash doesn't have a <<
method, that method in your example exists on the array.
The reason your code is not erroring is because you are passing a default value to your hash via the constructor. http://ruby-doc.org/core-1.9.3/Hash.html#method-c-new
hash = Hash.new([])
This means that if a key does not exist, then it will return an array. If you run the following code:
hash = {} hash[:one] << "uno"
Then you will get an undefined method error.
So in your example, what is actually happening is:
hash = Hash.new([]) hash[:one] << "uno" #hash[:one] does not exist so return an array and push "uno" hash[:two] << "dos" #hash[:two] does not exist, so return the array ["uno"] and push "dos"
The reason it does not return an array with one element each time as you may expect, is because it stores a reference to the value that you pass through to the constructor. Meaning that each time an element is pushed, it modifies the initial array.
When you're doing hash = Hash.new([])
you are creating a Hash whose default value is the exact same Array instance for all keys. So whenever you are accessing a key that doesn't exist, you get back the very same Array.
h = Hash.new([]) h[:foo].object_id # => 12215540 h[:bar].object_id # => 12215540
If you want one array per key, you have to use the block syntax of Hash.new
:
h = Hash.new { |h, k| h[k] = [] } h[:foo].object_id # => 7791280 h[:bar].object_id # => 7790760
Edit: Also see what Gazler has to say with regard to the #<<
method and on what object you are actually calling it.
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