#---Hash---

Hashes, like arrays are a powerful and commonly used data structure in Ruby. In some languages they’re known as dictionaries or maps.

With array, you can only access an item by its index:

arr = ['Bob', 23]
p "#{arr[0]}'s age is #{arr[1]}"
## => "Bob's age is 23"

but with a hash you can name the items and access them by their names (keys):

hash = {staff: 'Bob', age: 23}
p "#{hash[:staff]}'s age is #{hash[:age]}"

Use arrays to store a list of items. But use hashes to store key-value pairs that you want to look up later by the key.

Creating a Hash

The simplest way to create a hash is with the literal syntax:

hash = {name: 'Bob', age: 23}
p hash # => {name: "Bob", age: 23}

Here, Bob and 23 are the values, and :name and :age are the corresponding keys for those values.

(Note that the keys are symbols, although written weirdly with the colon after the word.

p hash.keys # => [:name, :age]

This is a new concise style of writing hashes that was introduced in Ruby 1.9. Prior to that, you had to do this:)

## This style is verbose and so has gone out of style.
hash = {:name => 'Bob', :age => 23}

You can also create a hash with Hash.new or Hash[] methods:

hash = Hash.new
p hash # => {}
hash = Hash[:name, 'Bob', :age, 23]
p hash # => {name: "Bob", age: 23}

Hash keys can be any object, including numbers, although it’s common to use strings or symbols. The values can also be any object, including other hashes.

h = {1 => 'one',
        'two' => 2,
        three: 3,
        'four' => {4 => 'four'}}
p h
## => {1 => "one", "two" => 2, three: 3, "four" =>
## {4 => "four"}}

Note: You still have to use the => operator for keys that are not symbols.

Since Ruby 3.1, you can create a hash even more tersely if the value variables are named the same as the keys:

a, b = 1, 2
h = {a: a, b: b} # pre-3.1
h = {a:, b:} # from 3.1+
p h # => {a: 1, b: 2}

Accessing a Hash Item

It’s as simple as this:

p hash[:name] # => "Bob"
p hash[:age] # => 23

But there’s a catch:
If you try to access a key that’s not in the hash, you’ll get nil, instead of some indicator that the key doesn’t exist:

p hash[:height] # => nil

Does the hash have a key :height and it’s value is nil? Or does the key not exist at all? (as in this example)

To avoid this, use has_key? to check if the key exists:

p hash.has_key?(:height) # => false

Or use fetch to access instead of []. It’ll blow up if the key doesn’t exist:

p hash[:height] # => nil
p hash.fetch(:height) rescue nil
## 💥 without `rescue nil`, blows up with KeyError

If you don’t want it to blow up, but just return a default value if the key doesn’t exist, you can do so like this:

p hash.fetch(:height, 123) # => 123

Default Values

If you want to set a default value for all keys that don’t exist, you can do so like this:

h = Hash.new('Default Joe')
p h[:name] # => "Default Joe"

Use the block version to set default value based on the key:

h = Hash.new { |hash, key| hash[key] = key.to_s.upcase }
p h[:name] # => "NAME"
p h[:age] # => "AGE"

Accessing Hash Items By Digging

Just like in arrays, you can use dig to access items from nested hashes. This is a great way to avoid errors:

dogs = {
  pongo: {age: 7, owner: "Roger" },
  perdita: {age: 5, owner: "Anita" },
}
p dogs[:pongo][:age] # => 7
p dogs[:perdita][:age] # => 5
p dogs[:colonel][:age] rescue nil
## 💥 without `rescue nil`, blows up with NoMethodError

Instead, use dig:

p dogs.dig(:colonel, :age) # => nil

It’s most useful in conditionals: Prior to dig, you had to do this:

if dogs[:pongo] && dogs[:pongo][:age]
end

With dig, you can do this:

if dogs.dig(:pongo, :age)
end

Note: dig also works with hashes containing arrays, that in turn contain hashes, and so on:

h = {a: [{b: 11}, {c: [22, 33]}]}
p h.dig(:a, 1, :c, 0) # => 22

Also note: An even better way is with pattern matching:

dogs => {perdita: {age: age_of_perdita}}
p age_of_perdita # => 5

Adding and Modifying Hash Items

Use []= to add a new item or modify an existing one:

h = {}
h[:name] = 'Bob'
p h[:name] # => "Bob"
h[:name] = 'Bobby'
p h[:name] # => "Bobby"

You can also use merge to add or modify multiple items at once. Here we’re modifying Bob’s name as well as adding a new key height:

bob = {name: 'Bob', age: 23}
p bob.merge(name: 'Bobby', height: 180)
## => {name: "Bobby", age: 23, height: 180}

Note: merge takes a hash as an argument, but you can see that the {} braces are missing. That’s because the braces are optional when the hash is the last argument in the method call.

Also note: merge doesn’t modify the original hash. It just returns a new hash:

p bob # => {name: "Bob", age: 23}

Use merge! to modify the original hash:

bob.merge!(name: 'Bobby', height: 180)
p bob # => {name: "Bobby", age: 23, height: 180}

Deleting Hash Items

Use delete to remove an item from a hash:

h = {bob: 23, alice: 25, charlie: 30}
p h.delete(:bob) # => 23
p h # => {alice: 25, charlie: 30}

Don’t assign nil to a key to delete it. This will just set the value to nil.

Use delete_if to delete items conditionally:

## Delete all older folks:
h.delete_if { |k, v| v > 27 }
p h # => {alice: 25}

Common Hash Methods

  • size gives you the count of hash items:
h = {bob: 23, alice: 25, charlie: 30}
p h.size # => 3
  • empty? returns true if hash has no items:
p h.empty?, {}.empty? # => [false, true]
  • keys returns an array of the keys. And values returns an array of the values:
p h.keys # => [:bob, :alice, :charlie]
p h.values # => [23, 25, 30]
  • has_key? returns true if the hash has the key. has_value? returns true if the hash has the value:
p h.has_key?(:bob), h.has_key?(:david) # => [true, false]
p h.has_value?(23), h.has_value?(100) # => [true, false]
  • compact removes all items with nil values:
h = {bob: 23, alice: nil, charlie: 30}
p h.compact # => {bob: 23, charlie: 30}
  • transform_keys is often used to convert string keys to symbols:
p ({'a' => 'aa', 'b' => 'bb'}).transform_keys(&:to_sym)
## => {:a => "aa", :b => "bb"}

Iterating Over a Hash

  • each is the simplest way to iterate over a hash. The block is called with each key-value pair:
h.each { |k, v| puts "#{k} is #{v} years old." }
## => bob is 23 years old.
## => alice is 25 years old.
## => charlie is 30 years old.
  • map returns a new array with the results of calling the block on each item:
p h.map { |k, v| "#{k} is #{v}" }
## => ["bob is 23", "alice is 25", "charlie is 30"]

Useful Enumerable Hash Methods

Like Array, the Hash class too gets a lot of its superpowers from the Enumerable module it includes.

Some useful enumerable methods:

  • map, discussed above.
  • select returns a new hash with only the items that make the block return a truthy value. reject does the opposite:
h = {bob: 23, alice: 25, charlie: 30}
p h.select { |k, v| v.even? } # => {charlie: 30}
p h.reject { |_, v| v.even? } # => {bob: 23, alice: 25}
  • group_by groups the items in the hash by the block’s return value:
h = {bob: 23, alice: 25, charlie: 30}
p h.group_by { |_, v| v.even? }
## => {true => [[:charlie, 30]],
## false => [[:bob, 23], [:alice, 25]]}
  • include? returns true if the hash contains the key:
p h.include?(:bob) # => true

Converting Hashes

  • to_a converts a hash to an array of arrays:
p h.to_a # => [[:bob, 23], [:alice, 25], [:charlie, 30]]
  • to_json converts a hash to a JSON string. But you need to require json first in some older versions of Ruby:
require 'json'
puts h.to_json # => {"bob":23,"alice":25,"charlie":30}

Official Docs