7 daily use cases of Ruby Hash
This is an archive of blog post I wrote during my third venture (PullReview).
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 Hash
es 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.
- How to convert a
JSON
into aHash
? - How to convert a
Hash
into aJSON
? - How to set default value for a nested
Hash
? - How to merge two nested
Hash
es? - How to filter out some keys of a
Hash
? - How to "sort" a
Hash
by value? - How to find the differences between two
Hash
es?
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:**
```ruby
=> "{"monday":2,
"tuesday":3,
"wednesday":4,
"thursday":20,
"friday":5,
"saturday":2,
x"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?