给定任何嵌套哈希,例如:
{ canada:
{ ontario:
{ ottawa: :me},
manitoba:
{ winnipeg: nil}},
united_states:
{ district_of_coloumbia:
{ washington: nil}}}
如何使用任意数组键[:canada, :ontario, :ottawa]
或[:united_states, :district_of_columbia, :washington]
来获取或设置值。
基本上,我的问题是当我不知道密钥数组的长度时,如何将[:canada, :ontario, :ottawa]
更改为格式为hash[:canada][:ontario][:ottawa]
的getter或setter。
所以我可以这样做:
hash[:canada][:ontario][:ottawa] = nil
hash[:canada][:manitoba][:winnipeg] = :me
我使用递归制作了一个getter:
def reindex(h, index_array)
i = index_array.shift
result = index_array.empty? ? h[i] : reindex(h[i], index_array)
result
end
但我觉得我在想这个,应该有一个更简单的方法。
答案 0 :(得分:2)
更简单的方法(在我看来)是使用:[]
连续访问元素:
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}}}
包装方法:
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}}}
答案 1 :(得分:2)
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
答案 2 :(得分:1)
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}}}
答案 3 :(得分:0)
我认为递归是你最好的选择。我不会考虑它"过度思考"我做了:
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
答案 4 :(得分:0)
是的,递归是一种选择。以下是如何实施的。
<强>代码强>
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
示例强>
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"}}
# }