Reputation: 529
Given any nested hash, for example:
{ canada:
{ ontario:
{ ottawa: :me},
manitoba:
{ winnipeg: nil}},
united_states:
{ district_of_coloumbia:
{ washington: nil}}}
how can I use any array of keys [:canada, :ontario, :ottawa]
or [:united_states, :district_of_columbia, :washington]
to get or set a value.
Basically, my problem is how do I change [:canada, :ontario, :ottawa]
into a getter or setter of the format hash[:canada][:ontario][:ottawa]
when I don't know the length of the array of keys.
so I can do something like:
hash[:canada][:ontario][:ottawa] = nil
hash[:canada][:manitoba][:winnipeg] = :me
I made a getter using recursion:
def reindex(h, index_array)
i = index_array.shift
result = index_array.empty? ? h[i] : reindex(h[i], index_array)
result
end
But I feel like I'm over thinking this and there should be a simpler way.
Upvotes: 4
Views: 117
Reputation: 3053
I think recursion is your best option. I wouldn't consider it "overthinking" I'd do:
def getter(hash, array)
return hash[array[0]] if array.count == 1
getter(hash[array[0]], array[1..-1], item)
end
def setter(hash, array, item)
return hash[array[0]] = item if array.count == 1
setter(hash[array[0]], array[1..-1], item)
end
Upvotes: 0
Reputation: 110725
Yes, recursion is an option. Here's how it could be implemented.
Code
def get(hash, arr)
case arr.size
when 1 then hash[arr.first]
else get(hash[arr.first], arr[1..-1])
end
end
def set(hash, arr, val)
case arr.size
when 1 then hash[arr.first] = val
else set(hash[arr.first], arr[1..-1], val)
end
end
Example
hash = {
canada: {
ontario:
{ ottawa: :me },
manitoba:
{ winnipeg: nil }
},
united_states: {
district_of_columbia:
{ washington: nil }
}
}
arr_can = [:canada, :ontario, :ottawa]
arr_us = [:united_states, :district_of_columbia, :washington]
get(hash, arr_can) #=> :me
get(hash, arr_us) #=> nil
set(hash, arr_can, 'cat')
set(hash, arr_us, 'dog')
hash
# => {:canada=>{:ontario=> {:ottawa=>"cat"},
# :manitoba=>{:winnipeg=>nil}},
# :united_states=>
# {:district_of_columbia=>{:washington=>"dog"}}
# }
Upvotes: 0
Reputation: 121
class Hash
def deep_fetch(*path)
path.reduce(self) do |mem, key|
mem[key] if mem
end
end
def deep_assign(*path, val)
key = path.shift
if path.empty?
self[key] = val
else
if self[key].is_a?(Hash)
self[key].deep_assign(*path, val)
else
self[key] = path.reverse.inject(val) { |a, n| {n => a} }
end
end
self
end
end
Upvotes: 2
Reputation: 15791
Much simpler approach(in my opinion) is to access elements successively with :[]
:
keys = [:canada, :ontario, :ottawa]
hash = { canada: { ontario: { ottawa: :me}, manitoba: { winnipeg: nil} }, united_states: { district_of_coloumbia: { washington: nil } } }
# get
p keys.inject(hash) { |h, k| h.public_send(:[], k) }
#=> :me
# set
last = keys[0..-2].inject(hash) { |h, k| h.public_send(:[], k) }
last.public_send(:[]=, keys[-1], 'other')
p hash #=> {:canada=>{:ontario=>{:ottawa=>"other"}, :manitoba=>{:winnipeg=>nil}}, :united_states=>{:district_of_coloumbia=>{:washington=>nil}}}
Wrapped in methods:
def get_by_keys(hash, keys)
keys.inject(hash) { |h, k| h.public_send(:[], k) }
end
def set_by_keys(hash, keys, v)
last = keys[0..-2].inject(hash) { |h, k| h.public_send(:[], k) }
last.public_send(:[]=, keys[-1], v)
hash
end
keys = [:canada, :ontario, :ottawa]
hash = { canada: { ontario: { ottawa: :me}, manitoba: { winnipeg: nil} }, united_states: { district_of_coloumbia: { washington: nil } } }
p get_by_keys(hash, keys) #=> :me
p set_by_keys(hash, keys, 'other') #=> {:canada=>{:ontario=>{:ottawa=>"other"}, :manitoba=>{:winnipeg=>nil}}, :united_states=>{:district_of_coloumbia=>{:washington=>nil}}}
Upvotes: 2
Reputation: 11
countries = {:canada=>{:ontario=>{:ottawa=>:me}, :manitoba=>{:winnipeg=>nil}}, :united_states=>{:district_of_coloumbia=>{:washington=>nil}}}
hash = Hash.new { |h, k| h[k] = Hash.new(&h.default_proc) }
hash.merge!(countries)
hash[:canada][:ontario][:ottawa] = nil
hash[:canada][:manitoba][:winnipeg] = :me
hash
=> {:canada=>{:ontario=>{:ottawa=>nil}, :manitoba=>{:winnipeg=>:me}}, :united_states=>{:district_of_coloumbia=>{:washington=>nil}}}
Upvotes: 1