Hashes

An introduction to the more descriptive and probably less familiar cousin of the Array.

Scroll down...

Content

Resources

Comments

In this lesson, we'll learn about Hashes, which many Ruby newbies have never seen before but which are incredibly useful data structures for storing information. Our goal, as usual, is for you to pick up how they work and the ways you'll interact with them while avoiding an overwhelming litany of methods to memorize.

What Are Hashes?

Hashes may be a bit intimidating at first but they're actually pretty similar to arrays. They're basically just containers for data, like arrays, but instead of storing that data in order based on numeric indices like in an array, you use "keys" which can be strings or symbols. This makes hashes more appropriate for storing data with a bit more depth to it.

So a Hash is just a container for data where each piece of data is mapped to a Key. The data is called the Value. Keys can be either strings or symbols. Values can be anything, just like with arrays.

In other languages, hashes are called "Dictionaries" because you look things up in them just like you would in the dictionary.

A hash looks almost like an array, but with squiggly braces {} instead of hard ones []. There's no order to a hash (unlike an array)... you're accessing the data using strings anyway so it doesn't matter which order they're in.

Creating a Hash

An empty hash can be made by using several methods:

> my_hash = Hash.new
#=> {}
> my_hash = {}        # easier way
#=> {}

Storing Data

You store data in a hash by matching a key with a value. Use the Hash Rocket => if you're creating the hash, or just index into it like an array using hard brackets [] if the hash already exists:

> favorite_colors = { "eyes" => "blue", "hair" => "blonde"}
#=> {"eyes"=>"blue", "hair"=>"blonde"} # new hash created
> favorite_colors["eyes"]
#=> "blue"

Modifying Data

Change Data in a hash just like you would with an array -- by indexing into it and assigning a new value. Unlike an array, to create a brand new data item, just pretend it already exists and assign it a value:

> favorite_colors = { "eyes" => "blue", "hair" => "blonde"}
#=> {"eyes"=>"blue", "hair"=>"blonde"}    # Created the hash
> favorite_colors["eyes"] = "green"       # Changing an item
#=> "green"
> favorite_colors
#=> {"eyes"=>"green", "hair"=>"blonde"}
> favorite_colors["skin"] = "sunburned"    # Adding a new item
#=> "sunburned"
> favorite_colors
#=> {"eyes"=>"blue", "hair"=>"blonde", "skin"=>"sunburned"}

Why Are Hashes Useful?

When might you use a hash? Hashes are useful for lots of reasons behind the scenes, but it should be immediately obvious that you can handle more nuanced data than you can with arrays. Perhaps if you need to store a dictionary of words? Just set the words as keys and the meanings as values.

Sometimes you need to keep track of an object which has several different attributes but isn't otherwise terribly complex. In this case, a hash can be a perfect solution to avoid using a bunch of different variables to store the same data:

# Data as lots of variables:
player_health = 100
player_name = "Player1"
player_speed = 7

# Data in a hash (better):
player = { "health" => 100, "name" => "Player1", "speed" => 7}

Hashes to Pass Options to Methods

You see hashes all the time in Rails, including as a way of passing options or parameters to a method (since they can store all kinds of different things and be variably sized), and these are often called Options Hashes.

Methods are often defined along the lines of def method_name arg1, arg2, arg3, options_hash, allowing the user to specify any number of different parameters for that method by just tacking them onto the end as additional "arguments".

# We'll set options to default to an empty hash
# so it doesn't raise a "wrong number of
# arguments" error if we don't specify the
# options argument when calling the method
def talk_box(words, options = {})
    spoken_words = words
    spoken_words.reverse! if options[:reverse]
    spoken_words.upcase! if options[:shout]
    puts spoken_words
end

# in IRB
> talk_box("howdy pardner!")
howdy pardner!
#=> nil
> talk_box("howdy pardner!",{:reverse=>true})
!rendrap ydwoh
#=> nil
> talk_box("howdy pardner!",{:reverse=>true,:shout=>true})
!RENDRAP YDWOH
#=> nil

Braces Are (Sometimes) Optional

If a hash is the last argument you are entering to a method, you can skip the squiggly braces. This is similar to how you can skip the parentheses when listing method arguments.

It's convenient, but can be a real head-scratcher for beginners who are trying to read code and wondering why there are methods being called with strangely mixed inputs and no braces. The biggest problem is that the last few inputs to the method appear to be independent arguments when they're actually just members of the options hash. The key is to look for the hash rocket (or the other syntax -- see below).

This can be illustrated using the example method above:

# With braces:
> talk_box("howdy pardner!",{:shout=>true})
HOWDY PARDNER!
#=> nil

# Without braces:
> talk_box "howdy pardner!", :shout => true
HOWDY PARDNER!
#=> nil

A common example of this happening in Rails is with the link_to method. link_to creates a link on the webpage and you can optionally assign it an ID and class (among other things like class and title attributes) by passing that in via the options hash:

link_to "click here!", "http://www.example.com", :id => "my-special-link", :class => "clickable_link"

Use Symbols As Keys

If you recall our discussion from Strings, we use symbols as keys for hashes more often than not.

> favorite_smells = { :flower => "daffodil", :cooking => "bacon" }
#=> { :flower => "daffodil", :cooking => "bacon" }

Alternative Syntax Using the Colon

People use symbols as keys for hashes so often that they got tired of writing out :some_key => "some_value". Instead, they combined the symbol colon with the hash rocket to put a colon after the key:

# the "old" way we've been using:
> user = {:email=>"foo@bar.com", :fname=>"foo", :lname=>"bar"}
#=> {:email=>"foo@bar.com", :fname=>"foo", :lname=>"bar"}

# the new way:
> user = {email: "foo@bar.com", fname: "foo", lname: "bar"}
#=> {:email=>"foo@bar.com", :fname=>"foo", :lname=>"bar"} 
# Note that IRB uses the old way for output!

This saves a few keystrokes but can be confusing too because it's not immediately obvious at a glance whether something is a hash. It also only applies if the key is a symbol.

Though it's fairly common in the real world to use the newer abbreviated syntax, it's much more readable, especially for beginners, to use the explicit syntax so we'll continue to favor that here.

Deleting a Key/Value Pair

Delete from a hash by just setting the value to nil or by calling the delete method:

> favorite_smells = { :flower => "daffodil", :cooking => "bacon" }
#=> { :flower => "daffodil", :cooking => "bacon" }
> favorite_smells[:flower] = nil
#=> nil
> favorite_smells
#=> {:cooking => "bacon" }   # one deleted...
> favorite_smells.delete(:cooking)
#=> "bacon"
> favorite_smells
#=> {}                       # ...and the other.

Combining Hashes

That's all pretty straightforward. What if we want to add two hashes together? Just use merge. If there are any conflicts, the incoming hash (on the right) overrides the hash actually calling the method.

> favorite_beers = { :pilsner => "Peroni" }
#=> { :pilsner => "Peroni" }
> favorite_colors.merge(favorite_beers)
#=> {"eyes"=>"blue", "hair"=>"blonde", "skin"=>"sunburned", :pilsner => "Peroni"}       
# okay, this is getting silly now...

Listing All Keys or Values

If you want to know what All the Keys are (more common) or All the Values are (less common) in a hash, just use the aptly named keys and values methods to spit them out as an array:

> favorite_colors.keys
#=> ["eyes", "hair", "skin", :pilsner]  
> favorite_colors.values
#=> ["blue", "blonde", "sunburned", "Peroni"]

Random Knowledge: Sets

A simpler kind of hash is called a Set, and it's just a hash where all the values are either True or False. It's useful because your computer can search more quickly through this than an array trying to store the same information due to the way it's set up behind the scenes.

This isn't a terribly common pattern but when computation time matters, you'll encounter optimizations like it.

> passed_classes_set = { :geometry => true, :algebra => true}
#=> { :geometry => true, :algebra => true}
> passed_classes_set.keys
#=> [:geometry, :algebra]


Sign up to track your progress for free

There are ( ) additional resources for this lesson. Check them out!

Sorry, comments aren't active just yet!

Next Lesson: Conditionals and Flow Control