如何在Ruby哈希中动态设置嵌套键的值

ds97pgxw  于 2023-10-18  发布在  Ruby
关注(0)|答案(5)|浏览(92)

应该很容易,但我找不到合适的解决办法。对于第一级密钥:

resource.public_send("#{key}=", value)

但是对于foo.bar.lolo呢?
我知道我可以得到它如下:

'foo.bar.lolo'.split('.').inject(resource, :send)

resource.instance_eval("foo.bar.lolo")

但是如何设置最后一个变量的值,假设我不知道嵌套级别,它可能是第二个或第三个。
是否有一个通用的方法来为所有级别做这件事?在我的例子中,我可以这样做:

resource.public_send("fofo").public_send("bar").public_send("lolo=", value)
ntjbwcob

ntjbwcob1#

如果你想允许初始化缺少的嵌套键,我推荐如下重构Aleksei Matiushkin' solution
我不想对实际的答案做任何改变,因为它是完全有效的,这是引入了一些额外的东西。

hash = { a: {} } # missing some nested keys
def deep_set(hash, value, *keys)
  keys[0...-1].inject(hash) do |acc, h|
    acc[h] ||= {} # initialize the missing keys (ex: b in this case) 
    acc.public_send(:[], h)
  end.public_send(:[]=, keys.last, value)
end

deep_set(hash, 42, :a, :b, :c)
#⇒ 42
hash
#⇒ { a: { b: { c: 42 } } }
beq87vna

beq87vna2#

假设已知密钥存在,那么Hash#dig给出了一个更清晰的解决方案:

hash = { a: { b: { c: 1 } } }

def deep_set(hash, value, *keys)
  hash.dig(*keys[0..-2])[keys[-1]] = value
end

deep_set(hash, 42, :a, :b, :c)
#⇒ 42

hash
#⇒ { a: { b: { c: 42 } } }

这只是示例代码。如果密钥未知或deep_set接收到的密钥少于两个,则无法正常工作。这两个问题都是可以解决的,但超出了OP的问题。

e3bfsja2

e3bfsja23#

回答哈希,只是出于好奇:

hash = { a: { b: { c: 1 } } }
def deep_set(hash, value, *keys)
  keys[0...-1].inject(hash) do |acc, h|
    acc.public_send(:[], h)
  end.public_send(:[]=, keys.last, value)
end

deep_set(hash, 42, :a, :b, :c)
#⇒ 42
hash
#⇒ { a: { b: { c: 42 } } }
eoigrqb6

eoigrqb64#

ruby中的散列在默认情况下不会给你给予这些点方法。
你可以链式发送调用(这对任何对象都有效,但你不能以这种方式正常访问哈希键):

"foo".send(:downcase).send(:upcase)

当使用嵌套哈希时,易变性的棘手概念是相关的。举例来说:

hash = { a: { b: { c: 1 } } }
  nested = hash[:a][:b]
  nested[:b] = 2
  hash
  # => { a: { b: { c: 2 } }

这里的“可变性”意味着当你将嵌套的哈希存储到一个单独的变量中时,它实际上仍然是一个指向原始哈希的指针。可变性在这种情况下很有用,但如果您不理解它,它也会产生错误。
您可以将:a:b分配给变量,使其在某种意义上是“动态的”。
有更高级的方法可以实现这一点,比如新的Ruby中的dig

versions.

  hash = { a: { b: { c: 1 } } }
  keys_to_get_nested_hash = [:a, :b]
  nested_hash = hash.dig *keys_to_get_nested_hash
  nested_hash[:c] = 2
  hash
  # => { a: { b: { c: 2 } } }

如果你使用OpenStruct,那么你可以给予你的散列点方法访问器。老实说,链接send调用不是我经常使用的东西。如果它能帮助你写代码,那很好。但是你不应该发送用户生成的输入,因为它是不安全的。

rvpgvaaj

rvpgvaaj5#

虽然你可以实现一些方法来做你现在设置的事情,但我强烈建议你重新考虑你的数据结构。
为了澄清一些术语,示例中的key不是一个键,而是一个方法调用。在Ruby中,当你有像my_thing.my_other_thing这样的代码时,my_other_thing总是一个方法,而不是一个键,至少不是这个术语的正确含义。
确实,您可以通过这种方式链接对象来创建类似哈希的结构,但这有一种真实的代码味道。如果您认为foo.bar.lolo是一种在散列中查找嵌套的lolo键的方法,那么您可能应该使用常规散列。

x = {foo: {bar: 'lolo'}}
x[:foo][:bar] # => 'lolo'
x[:foo][:bar] = 'new_value' # => 'new_value'

此外,尽管send/instance_eval方法可以这样使用,但这不是最佳实践,甚至会产生安全问题。

相关问题