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?
If you have any comment, question, or feedback, please share them with me.