Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does shovel (<<) operator work in Ruby Hashes?

Tags:

ruby

hash

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?

like image 587
bits Avatar asked Feb 18 '12 18:02

bits


People also ask

What is the shovel operator Ruby?

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.

How does Ruby hash work?

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.

Do Ruby hashes have an order?

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.

What is a hash table Ruby?

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.


2 Answers

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.

like image 123
Gazler Avatar answered Sep 21 '22 13:09

Gazler


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.

like image 37
Dominik Honnef Avatar answered Sep 21 '22 13:09

Dominik Honnef