Lesson 2
Exploring and Manipulating Ruby Hashes
Introduction to Ruby Hashes

Welcome to our exploration of data structures in Ruby! Today, we will delve into Ruby Hashes. Similar to a dictionary or an address book, hashes store data in key-value pairs, allowing for efficient data retrieval and modification. Hashes are essential in Ruby for managing and organizing data with unique identifiers. Let's dive into Ruby hashes to understand these concepts clearly.

Ruby Hashes

Ruby hashes are key-value collections that store data in pairs. They allow you to quickly access a value using its key. For example, a hash can store a friend's contact information, where their name (key) maps to their phone number (value).

Ruby
1contacts = { 2 "Alice" => "123-456-7890", 3 "Bob" => "234-567-8901" 4} 5 6puts contacts["Alice"] # Output: "123-456-7890" 7puts contacts["Carol"] # Output: nil (key not found)

In this example, we create a simple hash with names as keys and phone numbers as values. Trying to access a nonexistent key, like "Carol," returns nil.

Operations with Hashes

Ruby hashes offer several operations for adding, updating, and accessing key-value pairs, making data management efficient.

Let's explore how each of these operations works one by one.

Adding or Updating Entries

You can add or update entries by assigning a value to a key. If the key exists, the value is updated; if it doesn’t, a new key-value pair is added.

Ruby
1contacts = {} 2contacts["Alice"] = "123-456-7890" 3contacts["Bob"] = "234-567-8901" 4puts contacts.inspect # Output: {"Alice"=>"123-456-7890", "Bob"=>"234-567-8901"}

This example demonstrates adding key-value pairs to a hash by directly assigning values.

Accessing Values with fetch

The fetch method retrieves the value associated with a key and lets you specify a default return value if the key doesn’t exist. This is useful to avoid exceptions when accessing keys that may not be present.

Ruby also provides the .key? method to check if a key exists in the hash. Using .key? can be helpful to verify a key’s presence before performing operations.

Ruby
1puts contacts.fetch("Alice", "Not found") # Output: "123-456-7890" 2puts contacts.fetch("Carol", "Not found") # Output: "Not found" 3puts contacts.key?("Alice") # Output: true 4puts contacts.key?("Carol") # Output: false

The fetch method provides a fallback value, making it safer than direct key access, while .key? checks for the presence of a key and returns a boolean value.

Here are the key differences between fetch and direct access:

  • fetch Method:

    • Retrieves the value for a given key.
    • Allows specifying a default value if the key is not found.
    • Raises an error if the key is missing and no default is provided.
  • Direct Access or [] Method:

    • Retrieves the value for a given key.
    • Returns nil if the key is not found.
    • Does not provide a way to specify a default value directly.
Deleting Entries

To remove an entry, use the delete method with the key. This is useful for managing the hash’s contents.

Ruby
1contacts.delete("Bob") 2puts contacts.inspect # Output: {"Alice"=>"123-456-7890"}

The delete method removes the specified key-value pair from the hash.

Iterating Through Hashes

Ruby provides easy ways to loop through hashes using methods like each, each_key, and each_value. These methods let you iterate over keys, values, or key-value pairs.

Ruby
1tasks = { "Buy milk" => "Pending", "Pay bills" => "Completed" } 2 3tasks.each do |task, status| 4 puts "#{task}: #{status}" 5end 6# Output: 7# Buy milk: Pending 8# Pay bills: Completed

In this example, each iterates over the hash, providing access to both the key and value in each loop iteration.

You can also use each_key or each_value if you need only the keys or values:

Ruby
1tasks.each_key { |task| puts "Task: #{task}" } 2# Output: 3# Task: Buy milk 4# Task: Pay bills
Nested Hashes

A hash in Ruby can contain other hashes, forming nested structures. This is useful for organizing complex data. For instance, a student record could contain a name as a key and a nested hash of subjects and grades as values.

Ruby
1class StudentDatabase 2 def initialize 3 @students = {} 4 end 5 6 def add_student(name, subjects) 7 # Adds students and their subjects with grades 8 @students[name] = subjects 9 end 10 11 def get_mark(name, subject) 12 @students.fetch(name, {}).fetch(subject, "N/A") 13 end 14 15 def print_database 16 # Prints each student's name along with their subjects and grades 17 @students.each do |name, subjects| 18 puts "Student: #{name}" 19 subjects.each do |subject, grade| 20 puts " Subject: #{subject}, Grade: #{grade}" 21 end 22 end 23 end 24end 25 26# Example usage 27student_db = StudentDatabase.new 28student_db.add_student("Alice", {"Math" => "A", "English" => "B"}) 29student_db.add_student("Bob", {"Math" => "C", "Science" => "B+"}) 30 31puts student_db.get_mark("Alice", "English") # Output: "B" 32puts student_db.get_mark("Alice", "History") # Output: "N/A" 33student_db.print_database 34# Output: 35# Student: Alice 36# Subject: Math, Grade: A 37# Subject: English, Grade: B 38# Student: Bob 39# Subject: Math, Grade: C 40# Subject: Science, Grade: B+

This example demonstrates how to manage a nested hash structure within a class, where each student has their own record of subjects and grades.

Hash Defaults

You can set a default value for a hash to return when a key is not found. This avoids returning nil and can make your code more predictable.

Ruby
1inventory = Hash.new(0) 2inventory["Apples"] += 10 3inventory["Oranges"] += 5 4 5puts inventory["Apples"] # Output: 10 6puts inventory["Bananas"] # Output: 0 (default value)

In this example, the hash is initialized with a default value of 0, which is returned for keys that haven’t been added yet.

Practical Example: Managing a Shopping Cart

Let’s see a practical use of hashes by simulating a shopping cart. This hash-based cart stores product names as keys and quantities as values.

Ruby
1class ShoppingCart 2 3 def initialize 4 # Initialize cart as an empty hash 5 @cart = Hash.new(0) 6 end 7 8 def add_product(product_name, quantity) 9 # Add or update the quantity of a product in the cart 10 @cart[product_name] += quantity 11 end 12 13 def remove_product(product_name) 14 # Remove a product from the cart if it exists 15 if @cart.key?(product_name) 16 @cart.delete(product_name) 17 else 18 puts "#{product_name} is not in the cart." 19 end 20 end 21 22 def show_cart 23 # Display the products and their quantities in the cart 24 if @cart.empty? 25 puts "Your shopping cart is empty." 26 else 27 @cart.each { |product, quantity| puts "#{product}: #{quantity}" } 28 end 29 end 30end 31 32# Example usage 33my_cart = ShoppingCart.new 34my_cart.add_product("Apples", 5) 35my_cart.add_product("Bananas", 2) 36my_cart.add_product("Apples", 3) # Updates quantity of Apples to 8 37 38my_cart.show_cart 39# Output: 40# Apples: 8 41# Bananas: 2 42 43my_cart.remove_product("Bananas") 44my_cart.show_cart 45# Output: 46# Apples: 8

In this example, we define a shopping cart system where product quantities are tracked as values in a hash. Using methods to add, remove, and display items, we demonstrate how a hash can efficiently manage a dynamic dataset like a shopping cart.

Lesson Summary and Practice

Great job! Today, we explored Ruby hashes, including how to create, access, and modify key-value pairs. We covered operations like adding, updating, deleting, and fetching values, as well as iterating over hash entries. We also examined nested hashes, hash defaults, and used fetch and .key? for safe access to values with a fallback or for checking key presence.

Practicing with hashes will deepen your understanding of Ruby data structures and prepare you for handling complex data sets. Experiment with creating, modifying, and nesting hashes to strengthen your skills!

Enjoy this lesson? Now it's time to practice with Cosmo!
Practice is how you turn knowledge into actual skills.