如何根据任意长度的“键路径”设置Ruby哈希表?

4

给定:

h = {foo: {bar: 1}}

如何在不知道有多少个键的情况下设置bar
例如:keys = [:foo, :bar]
h[keys[0]][keys[1]] = :ok

但是,如果键可以是任意长度,并且 h 的深度也是任意的呢?


1
你是在问,给定一个键的列表(在一个数组中),设置最后一个键的值吗? - Anthony
@Anthony - 是的,更加简洁。 - B Seven
2个回答

8
如果你正在使用 Ruby 2.3+ 版本,你可以使用 dig 方法来实现如下功能:
h.dig(*keys[0..-2])[keys.last] = :ok
dig方法通过哈希表中的路径查找并返回结果。但是dig不会复制所找到的内容,因此您将得到与h中相同的引用。keys[0..-2]获取除keys中最后一个元素外的所有元素,因此h.dig(*keys[0..-2])h内部获取{bar: 1}哈希表,然后可以使用简单的赋值在原地修改它。
*head, tail = keys
h.dig(*head)[tail] = :ok

如果对您来说比 [0..-2] 更清晰,可以使用该方式。

如果您没有 dig,则可以进行以下操作:

*head, tail = keys
head.inject(h) { |m, k| m[k] }[tail] = :ok

当然,如果您不确定keys指定的路径是否存在,则需要添加一些nil检查,并决定如何处理未指定到hkeys

1
我试图使用dig,但无法弄清如何完成任务。我真的很喜欢你的方法。 - Anthony
1
我想到了 path = keys.dup; last_key = path.pop,但是 *head, tail = keys 更好。 - B Seven
1
对于那些寻找无脑解决方案的人,使用 dig 的解决方案假定 keys.size >= 2 - ribamar
我有一些嵌套方法的对象,它们没有 dig 方法,所以在这里似乎只能使用 inject 方法 =/!我甚至尝试过使用 dig 方法来访问值,但是对于设置操作,我得到了不同的 ActionController::Parameters 对象的 object_ids,或者使用某些语句设置值的次数很奇怪等问题,因此我可能会将 inject 代码转换为 Hash#bury - Pysis
1
如果您想在进行构建时创建不存在的路径,您可以使用(在最后一个示例中)head.inject(h) { |m, k| m[k] ||= {} }[tail] = :ok - Alex Davis

0
你可以像这样递归地完成它:
h = {foo: {bar: 1}}
keys = [:foo, :bar]

h2 = {
  foo: {
    bar: {
      baz: {
        setting: :bad
      }
    }
  }
}

keys2 = [:foo, :bar, :baz, :setting]

def set_nested_value(hash, keys, new_val)
  if keys.length == 1
    hash[keys.first] = new_val
    return
  end
  keys.each_with_index do |key, i|
    if hash[key]
      set_nested_value(hash[key], keys[i+1..-1], new_val)
    end
  end
end

set_nested_value(h, keys, :ok)
p h
=> {:foo=>{:bar=>:ok}}

set_nested_value(h2, keys2, :good)
p h2

=> {:foo=>{:bar=>{:baz=>{:setting=>:good}}}}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接