One liner nested hash creation in Ruby? (I come from Perl)
I am a Perl person and I have made Hashes like this for a while:
my %date; #Assume the scalars are called with 'my' earlier $date{$month}{$day}{$hours}{$min}{$sec}++
Now I am learning Ruby and I have so far found that using this tree is the way to do many keys and a value. Is there any way to use the simple format that I use with Perl using one line?
@date = { month => { day => { hours => { min => { sec => 1 } } } } }
Answers
Unfortunately, there is no simple, practical way. A Ruby equivalent would be an ugly, ugly beast like:
((((@date[month] ||= {})[day] ||= {})[hours] ||= {})[min] ||= {})[sec] = 1
There is a way to assign default values for missing keys in hashes, though:
@date = Hash.new { |hash, key| hash[key] = {} } # @date[:month] is set to a new, empty hash because the key is missing. @date[:month][:day] = 1
Unfortunately this does not work recursively.
...unless you create it yourself; hooray for Ruby!
class Hash def self.recursive new { |hash, key| hash[key] = recursive } end end @date = Hash.recursive @date[month][day][hours][min][sec] = 1 # @date now equals {month=>{day=>{hours=>{min=>{sec=>1}}}}}
Keep in mind, though, that all unset values are now {} rather than nil.
Here's a couple of options similar to the answer given by @molf but without the monkey patch.
Using a factory method:
def hash_tree Hash.new do |hash, key| hash[key] = hash_tree end end @date = hash_tree @date[month][day][hours][min][sec] = 1
With a custom class:
class HashTree < Hash def initialize super do |hash, key| hash[key] = HashTree.new end end end @date = HashTree.new @date[month][day][hours][min][sec] = 1
->f{f[f]}[->f{Hash.new{|h,k|h[k]=f[f]}}]
Obviously.
Compared to the lambda expression given above, this is simpler and also in one line:
Hash.new {|h,k| h[k] = Hash.new(&h.default_proc) }
Using symbols seemed to work:
ree-1.8.7-2009.10 > @date = {:month =>{:day => {:hours => {:min => {:sec => 1 } } } } } => {:month=>{:day=>{:hours=>{:min=>{:sec=>1}}}}}
I can then retrieve the val like this:
ree-1.8.7-2009.10 > @date[:month][:day] => {:hours=>{:min=>{:sec=>1}}}
It doesn't look like Ruby can do autovivification from the start, but you can easily add in that functionality. A search for "ruby autovivification" on Google gives:
http://t-a-w.blogspot.com/2006/07/autovivification-in-ruby.html
Which contains a decent example of how to create a hash that will work the way you are looking for.
ruby hash autovivification (facets) might also be helpful.
You can use the Facets gem's Hash.autonew to do the same thing as the recursive function given in Molf's answer.
require 'xkeys' date = {}.extend XKeys::Hash date[month, day, hours, min, sec, :else => 0] += 1