Les Is More
Updated: 31 Jan 2008
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.
BackThis is the website of Leslie Viljoen. More info
2021
March
2015
September
2014
December
September
July
April
March
February
January
2013
April
March
January
2012
July
2011
April
2008
January