24
h = {
  data: {
    user: {
      value: "John Doe" 
    }
  }
}

要将值分配给嵌套哈希,我们可以使用

h[:data][:user][:value] = "Bob"

但是,如果缺少中间的任何部分,则会导致错误。

就像是

h.dig(:data, :user, :value) = "Bob"

不会工作,因为还没有Hash#dig=可用的。

为了安全地赋值,我们可以做

h.dig(:data, :user)&.[]=(:value, "Bob")    # or equivalently
h.dig(:data, :user)&.store(:value, "Bob")

但是有更好的方法吗?

4

6 回答 6

14

它并非没有警告(如果您从其他地方接收哈希则不起作用),但一个常见的解决方案是:

hash = Hash.new {|h,k| h[k] = h.class.new(&h.default_proc) }

hash[:data][:user][:value] = "Bob"
p hash
# => { :data => { :user => { :value => "Bob" } } }
于 2016-01-05T20:53:04.840 回答
7

并以@rellampec 的答案为基础,那些不会引发错误的答案:

def dig_set(obj, keys, value)
  key = keys.first
  if keys.length == 1
    obj[key] = value
  else
    obj[key] = {} unless obj[key]
    dig_set(obj[key], keys.slice(1..-1), value)
  end
end

obj = {d: 'hey'}
dig_set(obj, [:a, :b, :c], 'val')
obj #=> {d: 'hey', a: {b: {c: 'val'}}} 
于 2020-04-24T12:33:08.660 回答
3

有趣的一个:

def dig_set(obj, keys, value)
  if keys.length == 1
    obj[keys.first] = value
  else
    dig_set(obj[keys.first], keys.slice(1..-1), value)
  end
end

如果没有[]or[]=方法,无论如何都会引发异常。

于 2019-04-28T05:07:41.103 回答
1

我找到了一个简单的解决方案来设置嵌套散列的值,即使缺少父键,即使散列已经存在。鉴于:

x = { gojira: { guitar: { joe: 'charvel' } } }

假设您想包含 mario 的鼓以产生:

x = { gojira: { guitar: { joe: 'charvel' }, drum: { mario: 'tama' } } }

我结束了猴子修补哈希:

class Hash

    # ensures nested hash from keys, and sets final key to value
    # keys: Array of Symbol|String
    # value: any
    def nested_set(keys, value)
      raise "DEBUG: nested_set keys must be an Array" unless keys.is_a?(Array)

      final_key = keys.pop
      return unless valid_key?(final_key)
      position = self
      for key in keys
        return unless valid_key?(key)
        position[key] = {} unless position[key].is_a?(Hash)
        position = position[key]
      end
      position[final_key] = value
    end

    private

      # returns true if key is valid
      def valid_key?(key)
        return true if key.is_a?(Symbol) || key.is_a?(String)
        raise "DEBUG: nested_set invalid key: #{key} (#{key.class})"
      end
end

用法:

x.nested_set([:instrument, :drum, :mario], 'tama')

您的示例的用法:

h.nested_set([:data, :user, :value], 'Bob')

我错过了什么警告?在不牺牲可读性的情况下编写代码有更好的方法吗?

于 2019-01-10T06:02:35.737 回答
0

在寻找类似问题的答案时,我偶然发现了一个类似于@niels-kristian 答案的界面,但还想支持命名空间定义参数,比如 xpath。

def deep_merge(memo, source)
  # From: http://www.ruby-forum.com/topic/142809
  # Author: Stefan Rusterholz
  merger = proc { |key, v1, v2| Hash === v1 && Hash === v2 ? v1.merge(v2, &merger) : v2 }
  memo.merge!(source, &merger)
end

# Like Hash#dig, but for setting a value at an xpath
def bury(memo, xpath, value, delimiter=%r{\.})
  xpath = xpath.split(delimiter) if xpath.respond_to?(:split)
  xpath.map!{|x|x.to_s.to_sym}.push(value)
  deep_merge(memo, xpath.reverse.inject { |memo, field| {field.to_sym => memo} })
end

嵌套哈希有点像 xpath,与digis相反bury

irb(main):014:0> memo = {:test=>"value"}
=> {:test=>"value"}
irb(main):015:0> bury(memo, 'test.this.long.path', 'value')
=> {:test=>{:this=>{:long=>{:path=>"value"}}}}
irb(main):016:0> bury(memo, [:test, 'this', 2, 4.0], 'value')
=> {:test=>{:this=>{:long=>{:path=>"value"}, :"2"=>{:"4.0"=>"value"}}}}
irb(main):017:0> bury(memo, 'test.this.long.path.even.longer', 'value')
=> {:test=>{:this=>{:long=>{:path=>{:even=>{:longer=>"value"}}}, :"2"=>{:"4.0"=>"value"}}}}
irb(main):018:0> bury(memo, 'test.this.long.other.even.longer', 'other')
=> {:test=>{:this=>{:long=>{:path=>{:even=>{:longer=>"value"}}, :other=>{:even=>{:longer=>"other"}}}, :"2"=>{:"4.0"=>"value"}}}}
于 2021-05-05T03:36:29.343 回答
0

@niels-kristian 答案的更类似于 ruby​​-helper 的版本

你可以像这样使用它:

a = {}
a.bury!([:a, :b], "foo")
a # => {:a => { :b => "foo" }}
class Hash
  def bury!(keys, value)
    key = keys.first
    if keys.length == 1
      self[key] = value
    else
      self[key] = {} unless self[key]
      self[key].bury!(keys.slice(1..-1), value)
    end
    self
  end
end
于 2022-01-28T08:07:58.910 回答