Politics, Programming and Possibilities
20 Sep
UPDATE: In the comments, Kent has provided a complete solution to this problem:
hash = Hash.new(&(p=lambda{|h,k| h[k] = Hash.new(&p)}))
I would love to see this integrated in to the Hash class at some point, but if not, we can (with Ruby’s kind acceptance) do it ourselves:
class Hash
def self.arbitrary_depth
Hash.new(&(p=lambda{|h,k| h[k] = Hash.new(&p)}))
end
end
Thus,
hash = Hash.arbitrary_depth
h = Hash.new
h[:one][:two] = “uh-oh!”
# NoMethodError: undefined method `[]=’ for nil:NilClass
The solution? Pass in a proc that will tell Hash to create a second hash by default for each key that is not found:
h = Hash.new { |h,v| h[v]= Hash.new }
h[:one][:two] = “uh-oh!”
# => “uh-oh!”
h
# => {:one=>{:two=>”uh-oh!”}}
Much better. But that’s only two-deep. What about n-deep? The solution I found isn’t fully “arbitrary” on demand, but it should work for most cases:
class ArbitraryDepthHash
def self.arbitrary_depth_proc(depth = 1)
proc { |h,v| h[v] = Hash.new(&arbitrary_depth_proc(depth-1)) }
end
def self.[](depth)
Hash.new(&arbitrary_depth_proc(depth))
end
end
h = ArbitraryDepthHash[5]
h[:one][:two][:three][:four][:five] = “wow!”
# => “wow!”
h
# => {:one=>{:two=>{:three=>{:four=>{:five=>”wow!”}}}}}
6 Responses for "Ruby Hashes of Arbitrary Depth"
Or
hash = Hash.new(&(p=lambda{|h,k| h[k] = Hash.new(&p)}))
Thanks, Kent!
Nice solution–I wish I’d thought of it!
Any chance I could get an explanation of what Kent’s solution does?
–mortal
Sure, Josh, I can take a gander:
hash = Hash.new(&(p=lambda{|h,k| h[k] = Hash.new(&p)}))
First you have to understand how a Hash allows you to set the default value of an empty key. Normally, the default is simply “nil”, meaning that if you access any old key, such as my_hash[:duane], without having first set :duane to something, then my_hash will return nil.
This default can be customized, like this:
my_hash = Hash.new { |h,k| h[k] = 0 }
In this case, my_hash[:brother] will return zero (0).
We could have just as well done this with a Proc object (instead of a block, as above) by doing something like this:
p = Proc.new { |h,k| h[k] = 0 }
my_hash = Hash.new(&p)
In this case, we’re using the Ruby notation ‘&’ to denote that the proc we’re passing in is to take the place of the optional block.
“lambda” is a synonym for “Proc.new”. Thus, Kent is creating a new Proc object assigned to the variable “p”. Because blocks (and thus, Proc objects, aka lambdas) are aware of the variable scope outside of themselves, Kent is able to pass the “p” he just barely assigned right back in to the Proc itself–essentially creating a recursive lambda function.
Thus, in English, this single line:
hash = Hash.new(&(p=lambda{|h,k| h[k] = Hash.new(&p)}))
says, “Create a hash whose default value is a hash whose default value is a hash whose default value is a hash whose …” etc.
Thanks a lot. This is the LISPy kind of thing I’ve been hoping to grasp as a potential solution to my own problems
Too much .NET hosed my brain…
Excellent writeup.
I’ve always used the HashMD class I found in the RubyTalk mailing list:
class HashMD {”a” => {”b” => {”c” => “xxx”}}}
http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/13408
Leave a reply