TL;DR
I want to do something like unix's mkdir -p
with a Ruby hash
hash = Hash.new {|h,k| h[k] = h.class.new(&h.default_proc) }
hash[:a][:b][:c] = 'Good!!'
p hash # {:a=>{:b=>{:c=>"Good!!"}}}
Hash.new
takes the hash itself and the key, and can define a default value in the block if the contents of the key do not exist.
hash1 = Hash.new { |h, k| 0 }
hash1[:a] = 100
p hash1[:a] # 100
p hash1[:b] # 0
The above simply defines a hash that returns 0 if the key is not found. Since : a
is set, it refers to the contents of the hash, and since: b
does not exist, the block is executed and 0 is returned.
Taking advantage of the property that the block is executed only if the key is not found, you can apply the default value to the hash itself as follows:
hash2 = Hash.new { |h, k| h[k] = 0 }
hash2[:a] += 100
hash2[:b] += 200
p hash2 # {:a=>100, :b=>200}
In the following, from the undefined state of both : a``: b
, addition processing is performed using + =
, but when hash2 [: a]
hash2 [: b]
is evaluated The default value of 0 is set in the hash itself, so 100 and 200 are set brilliantly.
Further application is to allow hash digging even when the keys are nested.
hash3 = Hash.new { |h,k| h[k] = {} }
hash3[:a][:b] = 100
p hash3 # {:a=>{:b=>100}
The block is executed when hash3 [: a]
is referenced, and the hash after hash3 [: a] = {}
is applied is returned. On the other hand, [: b] = 100
is executed, so the hash is finally nested.
However, if this is left as it is, it will fail when the nesting is tripled.
hash3 = Hash.new { |h,k| h[k] = {} }
hash3[:a][:b][:c] = 100 # undefined method `[]=' for nil:NilClass (NoMethodError)
That's because hash3 [: a]
sets the default value, but it's just a {}
, which itself doesn't define a default value setting.
Therefore, hash3 [: a] [: b]
is just a hash with no default value, so if you try to refer to hash3 [: a] [: b] [: c]
, you will get an error. is.
recursively
with default valuesThe hash3 [: a] [: b]
in the previous section has become a normal hash with no default value, but I would like it to have a similar default value.
To write it straightforwardly, it is as follows.
hash4 = Hash.new { |h,k| h[k] = Hash.new { |h,k| h[k] = {} } }
hash4[:a][:b][:c] = 100
p hash4 # {:a=>{:b=>{:c=>100}}}
We were able to handle even Mie Nest. However, of course, it cannot support quadruple nesting. It's hell.
hash4 = Hash.new { |h,k| h[k] = Hash.new { |h,k| h[k] = {} } }
hash4[:a][:b][:c][:d] = 100 # undefined method `[]=' for nil:NilClass (NoMethodError)
No matter how deep the nesting is, we want the hash to be the default value, which recursively firmly defines the default value.
You can use Hash # default_proc. default_proc
can get the block of default value definition set in the hash with the proc
object.
proc
can be executed by the call
method, so if you look at the following, you will get an image.
hash5 = Hash.new { |k, v| 'default value'}
p hash5.default_proc.call(nil, nil) # "default value"
And the block can be passed in the form & proc
, so you can write it like this:
hash6 = Hash.new { |h, k| h[k] = Hash.new(&h.default_proc) }
hash6[:a][:b][:c][:d] = 100
p hash6 # hash6[:a][:b][:c][:d] = 100
Did you get hungry?
hash6
makes Hash
, which applies definition of its own default value
, to its own default value
.
This meant that the definition of the default value would always be inherited, no matter how many nests there were.
After writing it, I noticed that there was an article with a commentary on a similar story. Well, I will post it in the sense of organizing what I learned.
-Nest hash infinitely -Define a recursively accessible Hash in Ruby
Recommended Posts