开发者

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 a开发者_开发技巧m 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                                                                                                                                                           
              }                                                                                                                                                                    
            }                                                                                                                                                                      
          }                                                                                                                                                                        
        }                                                                                                                                                                                                                                                                                                                                                     
      }                   


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


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) }


->f{f[f]}[->f{Hash.new{|h,k|h[k]=f[f]}}]

Obviously.


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.


1) Refinements

You can use the Ruby Refinements instead of Monkey Patching.

It is way more secure than MonkeyPatching the entire Hash class and the usage part is still fancy

Setup

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜