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

Need Your Help

What happens to timer in standby mode?

c# .net timer sleep

I'm using Timer from Timers namespace.

SQL Query for Selecting Multiple Records

mysql sql

I have 9 fields and I need to see all the data from these fields which have a particular set of IDs. Could any one tell me the SQL query for it?