A hash is a collection of key-value pairs. It's similar to a dictionary in other programming languages.
Each value in a hash is associated with a unique key, which is used to access that value. The keys and values can be any object (strings, symbols, numbers, etc.).
Here's a quick example of Ruby hashes. You can read the rest of the tutorial to learn more.
Example
# Using symbol keys
person = { name: "Gary", age: 32, city: "London" }
# Using string keys and hash rockets ( => )
student = { "name" => "Ash", "age" => 10, "grade" => "A" }
puts person
puts student
# Output:
# {name: "Gary", age: 32, city: "London"}
# {"name" => "Ash", "age" => 10, "grade" => "A"}
Here, the person
hash has keys of Symbol
type and uses colons :
to separate the keys and values. The student
hash has keys of String
type and uses hash rockets =>
to separate the keys and values.
Creating Hashes
You can create hashes in one of two ways:
- Using symbols as keys and colons
:
to separate keys and values. - Using hash rockets
=>
to separate keys and values.
1. Using Symbol Keys
The syntax to create hashes with symbol keys is:
hash_name = { key1: value1, key2: value2...keyN: valueN }
Here, the keys and values are separated by colons :
. For example,
person = { name: "Gary", age: 32, city: "London" }
Symbols use less memory than strings, and symbol comparisons are faster. Therefore, you should use symbol keys when possible.
Note: Symbol keys are more common and preferred in modern Ruby.
2. Using Hash Rockets (=>)
The syntax to create hashes with hash rockets =>
is:
hash_name = { key1 => value1, key2 => value2...keyN => valueN }
Here, the keys and values are separated by =>
. For example,
person = { "name" => "Gary", "age" => 32, "city" => "London" }
This is the classic syntax for creating hashes, and you should use it if your keys cannot be represented by the Symbol
type.
Note: For ease of understanding, we'll use the hash rocket =>
syntax in our initial examples, and then switch to the symbol syntax.
Hash Keys Are Unique
If a hash has duplicate keys, Ruby will issue a warning and only retain the last assignment of the duplicate key. For example,
# Hash with a duplicate key "age"
person = { "name" => "Gary", "age" => 32, "age" => 25 }
puts person
Output
main.rb:2: warning: key "age" is duplicated and overwritten on line 2 {"name" => "Gary", "age" => 25}
Here, the hash has two keys named "age"
. Ruby overwrites the earlier one with the latest.
In other words, it eliminates the key-value pair "age" => 32
but retains "age" => 25
.
Tip: Create your own hash with more than two duplicates of the same key and see what happens.
Accessing Hash Values
We can access a value in the hash by using the key inside square brackets []
. If the key does not exist, Ruby returns nil
. For example,
person = { "name" => "Gary", "age" => 32, "city" => "London" }
# Access values whose keys exist
puts person["name"]
puts person["age"]
puts person["city"]
# Access value of a non-existent key
# Returns "nil" so nothing gets printed on the screen
puts person["country"]
# Print the class/type of the "nil" output
puts person["country"].class
Output
Gary 32 London NilClass
Here's how this program works:
Code | Description | Output |
---|---|---|
person["name"] |
Returns the value associated with the key "name" . |
Gary |
person["age"] |
Returns the value associated with the key "age" . |
32 |
person["city"] |
Returns the value associated with the key "city" . |
London |
person["country"] |
Returns the value associated with the key "country" (which doesn't exist). |
This returns nil because the key doesn't exist. In Ruby, nil means "nothing". So nothing is printed. |
As you can see, we can access any value using the syntax hash_name[key]
. However, the key must exist, otherwise we'll get a blank output or nil
.
Modifying a Hash
You can update or add new key-value pairs using the assignment operator =
. For example,
person = { "name" => "Gary", "age" => 32, "city" => "London" }
puts "Original Hash:"
puts person
# Update an existing key
person["name"] = "Robert"
# Add a new key
person["country"] = "UK"
puts "\nModified Hash:"
puts person
Output
Original Hash: {"name" => "Gary", "age" => 32, "city" => "London"} Modified Hash: {"name" => "Robert", "age" => 32, "city" => "London", "country" => "UK"}
As you can see, you can use the following syntax to add new key-value pairs or to modify existing ones:
hash_name[key] = value
Accessing and Modifying Values with Symbol Keys
Let's access the values of a hash with keys of Symbol
type.
person = { name: "Gary", age: 32, city: "London" }
# Modify existing key
person[:age] = 25
# Add a new key
person[:country] = "UK"
# Access values
puts "Name: #{person[:name]}"
puts "Age: #{person[:age]}"
puts "City: #{person[:city]}"
puts "Country: #{person[:country]}"
Output
Name: Gary Age: 25 City: London Country: UK
In this example, we've used a hash with symbol keys.
When accessing and modifying/creating keys, notice that the symbol keys are written with the colon before the text:
:name
:age
:city
:country
This is because symbols are usually written with a colon before the name, like :age
.
But when creating a hash, Ruby allows a cleaner syntax where the colon comes after the key name:
person = { name: "Gary", age: 32, city: "London" }
You can still use the normal symbol notation and put the colon before the symbol text, but then you'll need to use hash rockets =>
:
person = { :name => "Gary", :age => 32, :city => "London" }
As you can see, this syntax is not as clean as the previous one.
Ruby Hash Methods
Ruby hashes have many handy methods you can use. Some common ones are:
Method | Description |
---|---|
keys |
Returns all keys in the hash. |
values |
Returns all values in the hash. |
fetch(k) |
Accesses the value associated with the key k and returns an error if the key doesn't exist. |
each |
Iterates over key-value pairs. |
key?(k) |
Checks if the key k exists in the hash. |
value?(v) |
Checks if the value v exists in the hash. |
delete(k) |
Deletes the key-value pair by key k . |
clear |
Removes all key-value pairs. |
empty? |
Returns true if the hash is empty. |
size |
Returns the number of key-value pairs. |
Note: key?
and value?
are aliases of the less-preferred has_key?
and has_value?
methods. We prefer key?
and value?
because it's cleaner and matches other predicate methods like empty?
, even?
, etc.
Example 1: Hash Methods to Print Keys and Values
person = { name: "Gary", age: 32, city: "London" }
# Print only the keys of the hash
puts "Hash Keys:"
puts person.keys
# Print only the values of the hash
puts "\nHash Values:"
puts person.values
puts "\nAccessing Values Using fetch:"
# Use fetch to access values
puts person.fetch(:name)
puts person.fetch(:age)
puts person.fetch(:city)
# Error: Attempt to access a non-existent key
puts person.fetch(:country)
Output
Hash Keys: name age city Hash Values: Gary 32 London Accessing Values Using fetch: Gary 32 London main.rb:17:in 'Hash#fetch': key not found: :country (KeyError)
As you can see, using fetch
to access a non-existent key results in an error.
Example 2: Hash Methods to Check if Keys and Values Exist
person = { name: "Gary", age: 32, city: "London" }
# Check if the :city key exists
print "Key :city Exists? "
puts person.key?(:city)
# Check if the :country key exists
print "Key :country Exists? "
puts person.key?(:country)
# Check if the value 32 exists
print "\nValue 32 Exists? "
puts person.value?(32)
# Check if the value "UK" exists
print "Value 'UK' Exists? "
puts person.value?("UK")
Output
Key :city Exists? true Key :country Exists? false Value 32 Exists? true Value 'UK' Exists? false
Example 3: Hash Methods to Delete Keys and Check Size
person = { name: "Gary", age: 32, city: "London" }
puts "Original Hash"
puts person
# Print the original size of the hash
print "Original Hash Size: "
puts person.size
# Delete :age key
person.delete(:age)
puts "\nHash After Deleting the Second Key"
puts person
# Print the new size of the hash
puts "\nNew Hash Size: #{person.size}"
# Check if the hash is empty
print "\nThe Hash is Empty? "
puts person.empty?
# Delete all key-value pairs
puts "\nClearing the Hash!"
person.clear
# Check if the hash is empty again
puts "\nThe Hash is Empty? #{person.empty?}"
Output
Original Hash {name: "Gary", age: 32, city: "London"} Original Hash Size: 3 Hash After Deleting the Second Key {name: "Gary", city: "London"} New Hash Size: 2 The Hash is Empty? false Clearing the Hash! The Hash is Empty? true
Iterating Over a Hash
You can use the each method to loop through a hash. For example,
person = { name: "Gary", age: 32, city: "London" }
person.each do |key, value|
puts "#{key}: #{value}"
end
Output
name: Gary age: 32 city: London
Iterating Over Only Keys Or Values
You can also iterate over just the keys or just the values like this:
person = { name: "Gary", age: 32, city: "London" }
puts "Keys:"
person.keys.each { |key| puts key }
puts "\nValues:"
person.values.each { |value| puts value }
Output
Keys: name age city Values: Gary 32 London
Nested Hashes
A hash can also contain other hashes. Such a structure is known as a nested hash. For example,
person = {
Gary: { age: 32, city: "London" },
Ash: {age: 10, city: "New York" }
}
# Print the person hash
puts person
# Access the hash associated with the key :Gary
# Then, print the value associated with :age
print "\nGary's Age: "
puts person[:Gary][:age]
# Access the hash associated with the key :Ash
# Then, print the value associated with :city
print "Ash's City: "
puts person[:Ash][:city]
Output
{Gary: {age: 32, city: "London"}, Ash: {age: 10, city: "New York"}} Gary's Age: 32 Ash's City: New York
Here, the hash person
has two other hashes nested inside of it. Here's how it works:
Key | Value |
---|---|
:Gary |
{age: 32, city: "London"} |
:Ash |
{age: 10, city: "New York" } |
As you can see, the keys have hashes as their values.
Frequently Asked Questions
Hashes and arrays operate differently and are useful in different situations.
Hashes store data in key-value pairs where each key is unique and maps to a specific value.
Arrays store data as an ordered list where each item has an index.
When to use hashes?
- When you need to store key-value pairs.
- When you need to store structured data with labels.
- When you need to search data by name instead of position.
When to use arrays?
- When you need to store an ordered list of items.
- When you care more about the position of the data than about labels.
Yes, you can use any object as a hash key. For example,
# Create an empty hash
hash = {}
# Create a pair with integer key
hash[1] = "integer key"
# Create a pair with array key
hash[[1, 2]] = "array key"
puts hash
Output
{1 => "integer key", [1, 2] => "array key"}
Here, we've used an integer key and an array key in our hash.
Important! Avoid using mutable objects (like arrays) as keys since their values can change, thus making them risky.
In Ruby, you can access values using either square brackets []
or using the fetch
method.
Why fetch is better?
However, using fetch
is better since it throws an error when you try to access the value of a key that doesn't exist. This helps in detecting errors and bugs in your code. For example,
person = { name: "Gary", age: 32, city: "London" }
# Error: Attempt to access a non-existent key
puts person.fetch(:country)
# Output: in 'Hash#fetch': key not found: :country (KeyError)
Square brackets don't throw an error.
By contrast, []
will only return nil
for non-existent keys. As a result, you'll find it difficult to debug your code:
person = { name: "Gary", age: 32, city: "London" }
# Attempt to access a non-existent key
# Code gives nil (blank output)
# No error message is printed
puts person[:country]
Default values can prevent errors in fetch.
You can avoid this error by using a default value with fetch. For example,
person = { name: "Gary", age: 32, city: "London" }
# Supply a default value
puts person.fetch(:country, "Not found")
# Output: Not found
Here, the fetch
method is supplied with a default value "Not found" that is returned whenever the key it's looking for doesn't exist.
Yes, you can provide a default value that will be returned when a key doesn't exist. There are multiple ways to do this, but let's look at three main ones:
1. Using .default after hash creation.
# Create an empty hash
person = {}
# Create a default value
person.default = "Key not found!"
# Hash that returns a message when the key is not found
person = Hash.new("Key not found!")
# Try to access a non-existent key
puts person[:name]
# Create a key-value pair
person[:name] = "Gary"
# Access the key again
puts person[:name]
Output
Key not found! Gary
Here, we have defined the string "Key not found!"
as the default value to the hash. Without it, Ruby returns nil
for missing keys.
2. Using the new method.
# Create a hash that returns a message
# when the key is not found
person = Hash.new("Key not found!")
# Try to access a non-existent key
puts person[:name]
Output
Key not found!
Here, the default value is supplied as an argument to the new
method.
3. Using a default value block with the new method.
You can also use the following syntax to assign a default value:
hash_name = Hash.new { |hash, key| block }
Here,
hash
- The new hash being created.key
- The key that was accessed but doesn't exist yet.block
- Defines what to do when a missing key is accessed.
For example,
person = Hash.new { |hash, key| hash[key] = "Key not found!" }
# Try to access a non-existent key
puts person[:name]
Output
Key not found!
In the above example, the code hash[key] = "Key not found!"
corresponds to the block
given in the syntax.
This code assigns the default value "Key not found!"
to any non-existent key
.
You can use the merge
method to merge two hashes:
hash1 = { x: 1, y: 2 }
hash2 = { y: 3, z: 4 }
merged_hash = hash1.merge(hash2)
puts merged_hash
Output
{x: 1, y: 3, z: 4}
The second hash overrides any duplicate keys. In our case, hash2
is the second hash because it's enclosed within the parentheses:
merged_hash = hash1.merge(hash2)
You can make hash1
the second hash with the following code:
merged_hash = hash2.merge(hash1)
Yes, you can use a for loop to iterate over a hash, but Ruby developers prefer the each
loop.
Example
person = { name: "Gary", age: 32, city: "London" }
for key, value in person
puts "#{key}: #{value}"
end
Output
name: Gary age: 32 city: London