Introducing Hashes

Learning Goals

  • Understand that there are multiple types of collections
  • Develop a mental model to understand hashes
  • Gain some familiarity with common hash methods

Slides

Available here

Vocabulary

  • Hash
  • Key
  • Value
  • Symbol
  • Accessing Values
  • Assigning Values

Warm Up

Would you rather store the list of fridge items in fridge_items_1 or fridge_items_2? Why? Discuss with your partner.

fridge_items_1 = ["milk", "eggs", "eggs", "eggs", "eggs", "eggs", "eggs", "avocado", "avocado", "tortilla", "tortilla", "tortilla", "tortilla", "tortilla", "tortilla", "tortilla", "tortilla", "tortilla"]
fridge_items_2 = {
	"milk" => 1,
	"eggs" => 6,
	"avocado" => 2,
	"tortilla" => 9
}

Intro - Hash Properties

Like an Array, a Hash is a data structure used for representing a collection of things. But whereas an Array generally represents a list of ordered, indexed values, a Hash represents a collection of named values. These names are called keys, and each key has a corresponding value. In a Hash, we can insert data by assigning it to a name and later retrieving it using the same name.

Some languages call their Hashes dictionaries for this reason – you look up a word (the label) to retrieve its definition (the data or value with which the label was associated).

Key ideas:

  • Ordered vs. Unordered
  • Pairs
  • Choosing a hash vs an array
  • Symbols vs. Strings
  • Performance characteristics

Working with a Hash

  • A hash is enclosed in curly braces { }, key/value pairs are separated by commas, and keys and values are separated by either a rocket or a colon.
  • Each key in a hash must be unique
    • If you attempt to have duplicate keys when you first create a hash, you will get a warning: key :key_name is duplicated and overwritten on line X error
    • If you try to add a new key/value pair using a key that already exists, that new key/value pair will overwrite the previous one - dangerous.
  • Keys and values can be any type of object:
      example = {	"string_value": "this value is a string",
                      "array_value": ["this", "value", "is", "an", "array"],
                      3: "this values' key is an integer",
                      "boolean_value": true
                  }
    
  • Values can be accessed with bracket notation:
    • given shih_tzu = { "name"=> "Sodie" }
    • shih_tzu["name"] returns"Sodie"

Let’s say we are making a list of items to pack for a trip. Why is a hash a good choice for storing this information?

THINK ABOUT IT: With your partner, brainstorm another collection of data that could be stored in a hash. WRITE: What is your definition of a hash?

Creating a Hash

new_hash = {}

or

new_hash = Hash.new

When using the Hash.new, syntax, we’re able to pass a default hash value in as a parameter to new.

new_hash = Hash.new(0)

In the above declaration, the default value of any key created for new_hash has a default value of 0. Keep this in mind for the future - you may find it helpful down the road.

We can also create a hash with some initial key/value pairs. Let’s use this syntax to create our stew hash:

suitcase = {
  "socks" => 4,
  "jeans" => 1,
}

The => is called a hash rocket.

Explore

With your partner, explore the following challenges. One partner should be typing (make sure the other can see the screen) and the other should talk. This is a paired programming technique called driver/navigator.

  • Start with the hash: suitcase = { “socks” => 4, “jeans” => 1 }
  • Add 3 shirts to your suitcase
  • Add a key value pair of swimsuit/true to your suitcase
  • Take the socks out of your suitcase
  • Check for how many jackets you have in your suitcase
  • Check how many shirts (and only shirts) are in your suitcase
  • Call .keys and .values on your hash - what is returned? Why might this be useful?

Accessing the Hash

We use brackets [] to access the hash just like arrays, only we don’t use indexes, we use keys.

suitcase["socks"]
=> 4

We can create a new key/value pair like this:

suitcase["shirts"] = 3
suitcase["swimsuit"] = true

Did we put any jackets on our list? Let’s check:

suitcase["jackets"]
=> nil

Oops, we forget that we added one day to our trip, so should probably bring an extra shirt.

suitcase["shirts"] = suitcase["shirts"] + 1

or

suitcase["shirts"] += 1

Remember, keys/values can be any type of object.

suitcase[8] = "this value is a string"
=> "this value is a string"
suitcase[true] = 1.5
=> 1.5
suitcase
=> {
  "socks"=>4,
  "jeans"=>1,
  "shirts"=>4,
  8=>"this value is a string",
  true=>1.5
}

In this code, we created a key with the Integer 8 with a value of the String “this value is a string”. We also created a key with true with a value of the Float 1.5.

We don’t want these pairs in our hash, so let’s get rid of them:

suitcase.delete(8)
=> "this value is a string"
suitcase.delete(true)
=> 1.5
suitcase
=> {
  "socks"=>4,
  "jeans"=>1,
  "shirts"=>4,
}

Access all the keys or all the values from a hash:

suitcase.keys
=> ["socks", "jeans", "shirts"]

suitcase.values
=> [4, 1, 4]

The .keys and .values methods return an array of all the keys and values, respectively, of the hash.

Check for Understanding

  • What is a Hash?
  • What type of Objects can Hashes hold?
  • How can you create a Hash?
  • How can you add/change/remove a key/value pair?

Symbols

In Ruby, symbols are basically Strings that can’t change. You can recognize a symbol because it starts with a colon :. All of the following are symbols:

:name   
:symbols_can_have_underscores
:"symbols can be in quotes"

Symbols are more efficient than strings because Ruby creates only one Object for each unique symbol. Two strings with the same value are still two separate Objects. This is illustrated in the following pry session:

sym_1 = :this_is_a_symbol
=> :this_is_a_symbol
sym_2 = :this_is_a_symbol
=> :this_is_a_symbol
sym_1.object_id
=> 2166748
sym_2.object_id
=> 2166748
string_1 = "this is a string"
=> "this is a string"
string_2 = "this is a string"
=> "this is a string"
string_1.object_id
=> 70099504860860
string_2.object_id
=> 70099508726060

Symbols are also faster than strings because Ruby can determine if two symbols are equal by checking their object_id. Strings have to be compared character by character.

So if symbols are faster and more efficient than strings, why would we use strings? Because a string’s value can change, making them useful as variables. Strings are mutable, whereas symbols are immutable.

Don’t worry if this doesn’t quite make sense yet. The important thing to understand is that strings are useful as variables. Symbols are useful as names. This makes symbols perfect for keys in hashes.

Working with Hashes and Symbols

Let’s recreate our suitcase hash using symbols instead of strings.

suitcase = {
  :socks => 4,
  :jeans => 1,
  :shirts => 3  
}

Ruby gives us a handy shortcut for creating a hash with symbol keys:

suitcase = {
  socks: 4,
  jeans: 1,
  shirts: 3
}

These two definitions for our suitcase hash produce the exact same hash, however the second is the preferred syntax. Be careful… The colon must immediately follow the name of the key without any spaces in between.

Let’s again add 2 potatoes:

suitcase[:swimsuit] = true

Get the number of socks:

suitcase[:socks]
=> 4

Check if we are packing any jackets:

suitcase[:jackets]
=> nil

And increase the amount of shirts by 1:

suitcase[:shirts] = suitcase[:shirts] + 1

or

suitcase[:shirts] += 1

If we want to see all of our keys/values…

suitcase.keys
=> [:socks, :jeans, :shirts, :swimsuit]
suitcase.values
=> [4, 1, 4, true]

What type of Objects do these methods return?

Check for Understanding

  • What is a symbol? How is it different than a String?
  • What is the advantage of using a String? What is the advantage of using a Symbol? Which is better for Hashes?
  • What is different about using symbols in Hashes?
  • Describe some useful Hash methods. Where can you look to find more Hash methods?

Pair Exercise

  • Person A is in charge of reading the instructions
  • Person B is in charge of working in pry (in such a way that their partner can see)
  • You should be using symbols for the keys in this exercise

Steps

  1. Create a hash called new_band.
  2. Add a bassist to your new_band hash.
  3. Find the name of your bassist by accessing the :bassist key in the new_band hash.
  4. Find the value attached to :vocalist in your hash.
  5. Add a vocalist to your hash.
  6. Add a drummer to your hash.
  7. Get all the keys in your Hash. What kind of object does that method return?
  8. Get all the values in your Hash. What kind of object does that method return?
  9. Assign a new value to the :vocalist key of your hash.
  10. How has keys changed after the last step? How has values changed? What

Extension Practice

Finally let’s break up for some independent work with Hashes and Arrays.

Hash and Array Nesting

Remember, keys/values can be any type of object, including Hashes and Arrays!

As our programs get more complex, we’ll encounter more sophisticated combinations of these structures. Consider the following scenarios:

Array within an Array

a = [[1, 2, 3], [4, 5, 6]]
  • what is a.count?
  • what is a.first.count?
  • how can I access the element 5?

Hash within an Array

italian = [{ pizza: "tasty" }, { calzone: "also tasty" }]
  • what is italian.count
  • what is italian.first.count
  • how can I access the value "also tasty"

Hash within a Hash

pets = {
  dog: {
    name: "Sodie",
    weight: "10 pounds"
  },
  cat: {
    name: "Sunshine",
    weight: "15 pounds"
  }
}
  • what is pets.count?
  • what is pets.keys?
  • what is pets.values?
  • how can I access the value "15 pounds"?

Exit Ticket

Further Practice/Resources

Ruby Docs

Get familiar with the Ruby Docs on Hashes

Practicing with Hashes and Nesting

Now that we’ve worked through the basics, complete Challenge 2 from the Collections Challenges

From the Top

Now you’ve got a decent understanding of hashes. Let’s go at it from the beginning and try to fill a few of the gaps: work through the Hashes section of Ruby in 100 Minutes to pickup a bit more.