These free mini-courses will give you a strong foundation in web development. Track your progress and access advanced courses on HTML/CSS, Ruby and JavaScript for free inside our student portal.
Scroll down...
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.
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.
An empty hash can be made by using several methods:
> my_hash = Hash.new
#=> {}
> my_hash = {} # easier way
#=> {}
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"
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"}
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}
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
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"
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" }
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.
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.
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...
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"]
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]