Ruby Hash of Array idioms

I often need a hash of arrays in Ruby. So for example, I can have:

1
2
3
months = {"Feb"=>["Article1", "Article2", "Article3"], 
          "Mar"=>["Article4", "Article5"]}

Now if I am reading through an array of articles, I can set up my hash like so:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Article
  attr_accessor :name, :month

  def initialize(name, month)
    @name = name
    @month = month
  end
end

articles = [
  Article.new("Article1", "Feb"), 
  Article.new("Article2", "Feb"), 
  Article.new("Article3", "Feb"), 
  Article.new("Article4", "Mar"), 
  Article.new("Article5", "Mar"), 
]

months = {}
articles.each do |a|
  months[a.month] ||= []
  months[a.month] << a.name
end

p months

This works great. For each article, we set a specific hash key to an empty array, but only if it is currently nil. That's what this does:

1
2
  months[a.month] ||= []

Then we add the article's name to the array:

1
2
  months[a.month] << a.name

There are more ways of doing that which might be better. You can combine the two lines:

1
2
3
4
5
months = {}
articles.each do |a|
  (months[a.month] ||= [])<< a.name
end

Another way is to provide a default for new hash values. When we create the hash (months = {}), we can specify a block which Ruby will use to generate default values in the hash. This makes things nicer:

1
2
3
4
5
months = Hash.new{|h, k| h[k] = []}
articles.each do |a|
  months[a.month] << a.name
end

Also, this way is a bit faster, since theoretically we are not testing each hash element for emptiness each time we add a value to the array. A benchmark does indeed show it to be slightly faster - though you need truly huge arrays to see any difference.

Back

Metadata

Home

Leslie

This is the website of Leslie Viljoen. More info



 
log in