#---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’re only given indices to the items, but with a hash you can name the items:

arr = ['Bob', 23]
p "#{arr[0]}'s age is #{arr[1]}"
## => "Bob's age is 23"
hash = {name: 'Bob', age: 23}
p "#{hash[:name]}'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. That’s because prior to Ruby 1.9, you had to declare the same hash like this:

## This is a bit verbose, and so has gone out of style.
## This is the 'hash-rocket' 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:}
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 doesn’t exist, you’ll get nil:

p hash[:height] # => nil

You might expect to get an error, but instead you silently got a nil.

To avoid this, use fetch. 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