如何使用命名索引(键)数组在嵌套哈希中设置值?

时间:2015-03-05 17:41:51

标签: ruby ruby-on-rails-4

给定任何嵌套哈希,例如:

{ 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

但我觉得我在想这个,应该有一个更简单的方法。

5 个答案:

答案 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"}}
  #    }