Everyday, you deal with Hashes. We use a lot of them everyday when coding PullReview, from Rails infamous 'params' to the various data we get from the GitHub JSON API.

Creating a new Hash or retrieving an element by its key, are common and simple to do. But when you need to merge 2 nested Hashes or filter some keys from one, you need to think a little about it. In the great doc, you'll find plenty of explanations for each method of Hash. As it's not case oriented, you won't quickly find how to resolve it. Below, I share 7 common use cases of Hash I met very often and should be useful to you.

  1. How to convert a JSON into a Hash?
  2. How to convert a Hash into a JSON?
  3. How to set default value for a nested Hash?
  4. How to merge two nested Hashes?
  5. How to filter out some keys of a Hash?
  6. How to "sort" a Hash by value?
  7. How to find the differences between two Hashes?

1. How to convert a JSON into a Hash?

You've just retrieved a Twitter profile as a JSON:

data = "{
  'name': 'Aaron Patterson',
  'screen_name': 'tenderlove',
  'location': 'Seattle, WA'
}"

You want to transform it as a Hash for easier data manipulation:

require "json"

profile = JSON.parse(data)

IRB Output:

 
=> {
  'name'=>'Aaron Patterson',
  'screen_name'=>'tenderlove',
  'location'=>'Seattle, WA'
}

Reference: JSON#parse


2. How to convert a Hash into a JSON?

In your web application, you track the number of signups per day for the current week:

signups_of_the_week = {
    monday: 2,
    tuesday: 3,
    wednesday: 4,
    thursday: 20,
    friday: 5,
    saturday: 2,
    sunday: 5
}

You can provide them through an API as JSON data:

require 'json'

signups_of_the_week.to_json

IRB Output:

=> "{"monday":2,
    "tuesday":3,
    "wednesday":4,
    "thursday":20,
    "friday":5,
    "saturday":2,
    "sunday":5}" 

Reference: JSON#generate
Side note: JSON#pretty_generate is really helpful for pretty printing and debugging.


3. How to set default value for a nested Hash?

You have collection of contacts indexed by name, a nested Hash:

contacts = {
  "John" => {
    name: "John",
    email: "john@doe.com"
  },
  "Freddy" => {
    name "Freddy",
    email: "freddy@mercury.com"
  }
}

When working with a contact, you don't want to check every time if it exists or not. You just want to write:

contacts["Jane"][:email] = "jane@doe.com"
puts contacts["Jane"]

IRB Output:

 => {:name=>'Jane', :email=>'jane@doe.com'}

You can do it by setting a complex value when creating the Hash

contacts = Hash.new do |hsh, key|
  hsh[key] = {
    name: key,
    email: ""
  }
end

or later with

contacts.default_proc = Proc.new do |hsh, key|
  hsh[key] = {
    name: key,
    email: ""
  }
end

References: Hash#new, Hash#default_proc=


4. How to merge two nested Hashes?

In an online shop, you want to merge a wish list with the current basket, both indexed by product id:

wish_list = {
  8 => {
    title: 'The Color of Magic',
  },
  42 => {
    title: 'The Hitch-Hiker"s Guide to the Galaxy',
    price: 5
  }
}

basket = {
  8 => {
    price: 10
  },
  1729 => {
    title: 'Ramanujan:  Twelve Lectures on Subjects Suggested by His Life and Work',
    price: 28
  }
}

With ActiveSupport, you can simply do it with:

require "active_support/core_ext/hash" # not necessary if in Rails

basket.deep_merge(wish_list)

or without ActiveSupport

def deep_merge(h1, h2)
  h1.merge(h2) { |key, h1_elem, h2_elem| deep_merge(h1_elem, h2_elem) }
end

deep_merge(basket, wish_list)

IRB Output:

 => {
  8=>{:price=>10, :title=>'The Color of Magic'},
  1729=>{:title=>'Ramanujan:  Twelve Lectures on Subjects Suggested by His Life and Work', :price=>28},
  42=>{:title=>'The Hitch-Hiker"s Guide to the Galaxy', :price=>5}
}

References: Hash#merge, Hash#deep_merge


5. How to filter out some keys of a Hash?

You've built an histogram of daily sales and stored it in a Hash, the key being the day:

histogram = {
  monday: 5,
  tuesday: 7,
  wednesday: 10,
  thursday: 18,
  friday: 7,
  saturday: 2,
  sunday: 0
}

You want to filter out Saturday and Sunday. With ActiveSupport,  you can do it as following

require "active_support/core_ext/hash" # not necessary if Rails

histogram.except(:saturday, :sunday)

or without ActiveSupport

def filter(hsh, *keys)
  hsh.dup.tap do |h|
    keys.each { |k| h.delete(k) }
  end
end

filter(histogram, :saturday, :sunday)

Another clearer implementation is based on reject (credits to Thiago A.):

def filter2(hsh, *keys)
  hsh.reject { |k, _| keys.include? k }
end

Note that if you deal with large collection you should benchmark your implementation to select the best one.

IRB Output:

 => {:monday=>5, :tuesday=>7, :wednesday=>10, :thursday=>18, :friday=>7}

Reference: Hash#except, Hash#delete, Hash#reject, Object#dup, Object#tap


6. How to "sort" a Hash by value?

In a dices game, you keep the score per player in a Hash:

scores = {
  "The Lady" => 3,
  "Fate" => 2,
  "Death" => 10
}

You want to to sort them by scores. You can do it with:

leaderboard = scores.sort_by { |_, score| -score }

IRB Output:

 => [['Death', 10], ['The Lady', 3], ['Fate', 2]]

Reference: Enumerable#sort_by
Side note: Hash enumerates their value in their insertion order.


7. How to find the differences between two Hashes?

You regularly retrieve data from RSS feeds and put them into a Hash:

entries = {
  1372284000 => 'CVE-2013-4073',
  1368482400 => 'CVE-2013-2065'
}

When doing an update, you get another Hash:

updated_entries = {
  1385074800 => 'CVE-2013-4164',
  1372284000 => 'CVE-2013-4073',
  1368482400 => 'CVE-2013-2065'
}

You want to spot which entries are new so you can for instance send them by email. You can do it with:

new_entries = updated_entries.dup.delete_if {|k| entries.keys.include? k }

but it's better and clearer to do with

new_entries = updated_entries.reject { |k, _| entries.include? k }

as you benefit from the fast lookup of Hash (Credits to apeiros)

IRB Output:

=> {1385074800=>'CVE-2013-4164'}

Reference: Hash#delete_if, Hash#keys, Hash#reject, Hash#include?


What’s your daily use cases of Hash?

After the treasures of Array, I really enjoyed to list those of Hash. What’s yours? What are your typical daily use cases of Hash and elegant Ruby to resolve them?