如何在不使用许多'nil'防护的情况下分配给深层嵌套的Hash

时间:2018-04-05 02:50:52

标签: ruby hash

我有一个嵌套的哈希,我需要添加更深层次的嵌套属性/值对。

示例A:

a = {}
a['x']['y']['z'] << 8

通常我必须这样做:

示例B:

a = {}
a['x']           ||= a['x']           = {}
a['x']['y']      ||= a['x']['y']      = {}
a['x']['y']['z'] ||= a['x']['y']['z'] = []

否则,我会得到undefined method '<<' for nil:NillClass

代码A中是否有某种类型的速记或函数而不是代码B?

4 个答案:

答案 0 :(得分:5)

任何深度的深层嵌套哈希的最优雅解决方案是:

hash = Hash.new { |h, k| h[k] = h.dup.clear }

或者,甚至更好(归功于@Stefan)

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

这样可以访问任何级别:

hash[:a1][:a2][:a3][:a4] = :foo
#⇒ {:a1=>{:a2=>{:a3=>{:a4=>:foo}}}}

想法是克隆哈希本身内的default_proc

答案 1 :(得分:1)

为了给一个背景,有一个方法Hash#dig,自红宝石2.3以来就存在。有了这个,您可以安全地尝试读取任意数量的键:

{a: {b: {c: 1}}}.dig :a, :b, :c # => 1
{}.dig :a, :b, :c # => nil

当然,这并不能解决您的问题。您正在寻找版本。这是Hash#bury形式的proposed but rejected in Ruby core

此方法几乎完全符合您的要求,但它只能设置嵌套的哈希值而不是附加到嵌套数组

# start with empty hash
hash = {}

# define the inner array
hash.bury :x, :y, :z, []

# add to the inner array
hash[:x][:y][:z] << :some_val

您可以通过ruby-bury gem获取此方法,也可以从their source code

获取实施方法

答案 2 :(得分:1)

以下是可以采取的几种方法。

arr = [:a1, :a2, :a3, :a4, :foo]

使用Enumerable#reduce(又名inject

def hashify(arr)
  arr[0..-3].reverse_each.reduce(arr[-2]=>arr[-1]) { |h,x| { x=>h } }
end    

hashify(arr)
  #=> {:a1=>{:a2=>{:a3=>{:a4=>:foo}}}}

使用递归

def hashify(arr)
  first, *rest = arr
  rest.size == 1 ? { first=>rest.first } : { first=>hashify(rest) }
end

hashify(arr)
  #=> {:a1=>{:a2=>{:a3=>{:a4=>:foo}}}}

答案 3 :(得分:0)

您可以考虑使用 get gem 中的 setrodash 方法,以便将深度嵌套的值设置为具有默认值的哈希值。

require 'rodash'

a = {}

key = ['x', 'y', 'z']
default_value = []
value = 8

current_value = Rodash.get(a, key, default_value)
Rodash.set(a, key, current_value << value)

a
# => {"x"=>{"y"=>{"z"=>[8]}}}