Lesson 1
Introduction to Ruby Hashes
Introduction to Hashes

Hi, and welcome! Today, we'll explore Hashes, a data structure that organizes data as key-value pairs, much like a treasure box with unique labels for each compartment.

Imagine dozens of toys in a box. If each toy had a unique label (the key), you could directly select a toy (the value) using the label. No rummaging required — that's the power of Hashes! Today, we'll understand Hashes and learn how to implement them in Ruby.

Understanding Hashes

Hashes are special types of collections that utilize unique keys instead of indexes. When you know the key (toy's label), you can directly pick up the value (toy). That's how a Hash works!

Consider an oversized library of books. With Hashes (which act like the library catalog), you can quickly locate any book using a unique number (key)!

Hashes in Ruby

Ruby implements key-value pairs through Hashes. They hold data in key-value pairs, enclosed within {} (curly braces).

Let's create a hash, functioning as a catalog for a library:

Ruby
1# Creating a catalog for the library using hashes 2library_catalog = {'book1' => 'A Tale of Two Cities', 3 'book2' => 'To Kill a Mockingbird', 4 'book3' => '1984'}

In this hash, 'book1', 'book2', and 'book3' are keys, while the book titles serve as their respective values.

It's important to remember that while hash values can be any type (mutable or immutable), hash keys should be immutable types, commonly strings or symbols.

Hash Operations: Accessing, Updating, and Removing Elements

Hashes allow you to access, update, or remove elements:

  1. Accessing Elements: You can retrieve a book's title using its key straightforwardly: library_catalog['book1'] would return 'A Tale of Two Cities'. However, if you try to access a key that isn't present in the hash, nil would be returned instead of an error.

    Alternatively, Ruby Hashes provide the fetch method. It raises an error if the key isn't present, but you can also provide a default value.

    Ruby
    1# Using fetch to access a book's title with a default value 2book1 = library_catalog.fetch('book1', 'Not Found') 3puts book1 # Output: "A Tale of Two Cities" 4 5# Using fetch to access a nonexistent key with a default value 6nonexistent_book = library_catalog.fetch('book100', 'Not Found') 7puts nonexistent_book # Output: "Not Found"

    In the example above, using fetch('book1') retrieves the value for the key 'book1', and fetch('book100', 'Not Found') provides a safe alternative to a missing key.

  2. Adding or Updating Elements: Whether you're adding a new book to the catalog or updating an existing book's title, you'll use the assignment operator (=). This syntax in Ruby Hashes allows for both updating existing key-value pairs and establishing new ones.

    If the specified key exists in the hash, the assigned value replaces the existing one. For updating a title: library_catalog['book1'] = 'The Tell-Tale Heart'.

    If the key doesn't exist in the hash yet, the operation creates a new key-value pair: library_catalog['book4'] = 'Pride and Prejudice'.

  3. Removing Elements: If 'book1' no longer exists, you can remove it using library_catalog.delete('book1').

Hash Methods: `each`, `keys`, `values`, and others

Ruby's Hashes offer several useful methods to interact with and manage your data:

  1. Checking for a Key: Verify whether a given book is present in your catalog using library_catalog.key?('book1').

  2. Accessing all Key-Value Pairs: Use the each method to iterate over all key-value pairs in the hash.

    Ruby
    1# Looping over the hash 2library_catalog.each do |key, value| 3 puts "#{key} : #{value}" 4end

    When run, this code prints:

    1book1 : A Tale of Two Cities 2book2 : To Kill a Mockingbird 3book3 : 1984
  3. Accessing all Keys and Values: The keys and values methods return arrays consisting of all keys and all values in the hash, respectively.

    Ruby
    1# Getting all keys 2all_keys = library_catalog.keys 3# all_keys now holds: ['book1', 'book2', 'book3'] 4 5# Getting all values 6all_values = library_catalog.values 7# all_values now holds: ['A Tale of Two Cities', 'To Kill a Mockingbird', '1984']
Understanding Time Complexity

Hashes are popular because they save time! Operations like addition, update, and locating elements usually take constant time, O(1), meaning they require nearly the same amount of time regardless of the library size. That’s because a hash in Ruby is implemented as a hash table, which maps keys (e.g., "book3") to values (e.g., "1984") using a hash function. The hash function takes the key and produces a hash code, which is an integer value that determines the memory location (or bucket) where the corresponding value will be stored. The values then can be accessed directly using its key.

What happens when two keys have the same hash code? Ruby resolves this by storing the colliding keys and values in a linked list or similar data structure in the same bucket. Although collisions can slightly increase the lookup time, modern hash functions are designed to minimize collisions, so the overall time complexity remains average O(1) for lookups.

Lesson Summary and Practice

Well done! You've mastered Hashes, understood Ruby's implementation of Hashes, learned their operations, and grasped the concept of time complexity. Now, gear up for some practice exercises to reinforce your learning. Happy Coding!

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