An Introduction to Ruby Hash
By: Mahbub Zaman
Tags: Data Science Training, Software Engineering, Programming, Ruby, Productivity

The basics

Let's start with some basic operations. Below we have an example of how to initialize an empty hash.

students = Hash.new

or

students = {}

Here we have an empty hash, without any key-value pairs. Now the question is, what if someone wants to access a key that does not exist in the hash? Well, the answer is nil , which is the default value.

puts "#{students[:name]}"

# nil

Let’s add some values to the hash using symbols. There are two ways to declare symbols as keys.

students = Hash.new

students = {name: 'John', email: 'john@col.com'}

puts "#{students[:name]}"

# or

students = Hash.new

students = {:name => 'John', :email => 'john@col.com'}

puts "#{students[:name]}"

I prefer the former one because I’ve to type less! How about initializing with default values, here we have initialized an empty hash with a default value of “no data” for every key.

students = Hash.new("no data")

puts "#{students[:email]}"

# no email

# or

students = Hash.new

students.default = "no data"

puts "#{students[:email]}"

# no email

The Hash.new() creates a single default object for every key. That’s why by altering it for one key changes for other keys as well. For example, when we change the default value for the key id , it affects other unassigned key’s default value, in this case, name .

students = Hash.new("no data")

students[:email] = "john@col.com"

puts "#{students[:email]}"

# john@col.com

puts "#{students[:id]}"

# no data

puts "#{students[:name]}"

# no data students[:id].upcase!

puts "#{students[:email]}"

# john@col.com

puts "#{students[:id]}"

# NO DATA

puts "#{students[:name]}"

# NO DATA

If the above code behavior is not desired, you can use Hash.new {|hash, key| block } instead. This method creates a default object each time for different keys.

students = Hash.new {|hash, key| hash[key] = "no data" }

students[:email] = "john@col.com"

puts "#{students[:email]}"

# john@col.com

puts "#{students[:id]}"

# no data

puts "#{students[:name]}"

# no data students[:id].upcase!

puts "#{students[:email]}"

# john@col.com

puts "#{students[:id]}"

# NO DATA

puts "#{students[:name]}"

# no data

In short, Hash.new(some_value) sets a default value of some_value for every key that does not exist in the hash, Hash.new {|hash, key| block } creates a new default object for every key that does not exist in the hash, and Hash.new() or {} sets nil for every key.

The essentials

So far, we have learned how to add values to the hash and access those values using their associated keys. Let’s explore some helpful hash methods. First, let’s print out the keys and values of a hash.

tags = { c: 20, b: 10, a: 30 }

tags.each {|key, value| puts "#{key} is #{value}" }

# c is 20

# b is 10

# a is 30

According to ruby documentation, “Hashes enumerate their values in the order that the corresponding keys were inserted”. Now we will sort the hash in different ways.

tags.sort

# output

# [[:a, 30], [:b, 10], [:c, 20]] tags.sort_by { |k, v| k }

# output

# [[:a, 30], [:b, 10], [:c, 20]] tags.sort_by { |k, v| v }

# output

# [[:b, 10], [:c, 20], [:a, 30]]

Since we have key-value pairs in our hash, we can use has_key? to find a specific key or has_value? to find value in the hash.

tags.has_key?(:e)

# false tags = { c: 20, b: 10, a: 30, e: 30} tags.has_key?(:e)

# true tags.has_value?(30)

# true

Why not print out the hash keys and values?

puts "#{tags.keys}"

# [:c, :b, :a, :e]

puts "#{tags.values}"

tags.values

# [20, 10, 30, 30]

Deleting a key-value pair from a hash is easy. The method delete takes the key as an argument, and it returns nil if the key does not exist or the value otherwise.

tags = { c: 20, b: 10, a: 30, e: 30}

tags.delete(:e) puts "#{tags.keys}"

Practical applications

Enough with the basics and essentials. Let’s dive into practical applications of ruby hash. The problem we are trying to solve is to get the frequency from a list of numbers. The first example deals with the built in method each_with_object .

numbers = [1, 1, 1, 2, 4, 65, 55, 54, 55]

freq_hash = numbers.each_with_object(Hash.new(0)) { |number, hash| hash[number] += 1 }

puts "#{freq_hash}"

# {1=>3, 2=>1, 4=>1, 65=>1, 55=>2, 54=>1}

For the second example, we are using the built-in method each .

numbers = [1, 1, 1, 2, 4, 65, 55, 54, 55]

freq_hash = Hash.new(0)

numbers.each { |number| freq_hash[number] += 1 }

puts "#{freq_hash}"

# {1=>3, 2=>1, 4=>1, 65=>1, 55=>2, 54=>1}

Good news if you are using Ruby 2.7+. It has a built-in method that solves this specific problem.

numbers = [1, 1, 1, 2, 4, 65, 55, 54, 55]

numbers.tally

# output

# {1=>3, 2=>1, 4=>1, 65=>1, 55=>2, 54=>1}

For the last example, we have a sample lookup table of the months.