Ruby 嵌套哈希动态获取

发布于 2024-12-08 12:41:19 字数 1296 浏览 0 评论 0原文

我在尝试使用密钥更新嵌套哈希时遇到问题。

我的嵌套哈希是这样的:

main_hash = {   
    "Energy"=>
      {"name"=>"Energy", "uri"=>"energy", 
      "children"=>
        {"Renewable Energy"=>{"name"=>"Renewable Energy", "uri"=>"energy/renewable_energy"}}}
    , 
    "Farming"=>
      {"name"=>"Farming", "uri"=>"farming", 
        "children"=>
        {"Organic Gas"=>
            {"name"=>"Organic Gas", "uri"=>"farming/organic_gas"
              "children" =>
                {"Gas Oil"=>{"name"=>"Gas Oil", "uri"=>"farming/organic_gas/gas_oil"}}
              }}}}

我想做的是更新哈希中的一个项目(例如,我想向“有机气体”添加另一个子项)。我知道我可以做到这一点:

  main_hash["Farming"]["children"]["Organic Gas"]["children"].merge!(another_hash)

问题是我需要动态获取它,因为它可能会嵌套得很深。

因此,为了达到所需的水平,我会这样做(其工作原理如上所述)。

main_hash.send(:fetch, "Farming").send(:fetch, "children").send(:fetch, "Organic Gas").send(:fetch, "children")

如果我可以像下面这样动态调用“发送”方法(显然它不起作用),那就太好了。

main_hash.send(:try, 'send(:fetch, "Farming").send(:fetch, "children").send(:fetch, "Organic Gas").send(:fetch, "children")')

我希望它能明确我想要实现的目标。我已经浏览了 Ruby Hash 的所有内置函数,但找不到适合我需要的函数。

任何帮助将不胜感激。

干杯。

I have an issue trying to update a nested Hash using a key.

The nested hash I have is like this:

main_hash = {   
    "Energy"=>
      {"name"=>"Energy", "uri"=>"energy", 
      "children"=>
        {"Renewable Energy"=>{"name"=>"Renewable Energy", "uri"=>"energy/renewable_energy"}}}
    , 
    "Farming"=>
      {"name"=>"Farming", "uri"=>"farming", 
        "children"=>
        {"Organic Gas"=>
            {"name"=>"Organic Gas", "uri"=>"farming/organic_gas"
              "children" =>
                {"Gas Oil"=>{"name"=>"Gas Oil", "uri"=>"farming/organic_gas/gas_oil"}}
              }}}}

What I would like to do is to update an item from the hash (e.g., I want to add another child to "Organic Gas"). I know I can do this:

  main_hash["Farming"]["children"]["Organic Gas"]["children"].merge!(another_hash)

The problem is I need to get that dynamically as it can get quite deeply nested.

So to get to the desired level, I would do this (which does work as above).

main_hash.send(:fetch, "Farming").send(:fetch, "children").send(:fetch, "Organic Gas").send(:fetch, "children")

It would really be great if I could call "send" method dynamically like below (obviously it won't work).

main_hash.send(:try, 'send(:fetch, "Farming").send(:fetch, "children").send(:fetch, "Organic Gas").send(:fetch, "children")')

I hope it makes it clear what I want to achieve. I have gone through all Ruby Hash's built in function and I can't get the one suited for my need.

Any help would be greatly appreciated.

Cheers.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

薄暮涼年 2024-12-15 12:41:19

我不确定哈希是否真的是最好的数据结构。你试图用它来表示一棵树,这很好,但如果你只是明确地将它变成一棵树,它可能会更清晰一些:(

class Tree
  attr_reader :name, :uri, :children, :parent

  def initialize(name, uri, *children)
    @children = children
    @name, @uri = name, uri
  end

  def <<(child)
    @children << child
  end

  def find(name)
    each_branch.detect {|branch| branch.name == name }
  end

  def leaf?
    @children.empty?
  end

  # The parameter `i` just decides whether or not to include self.
  def each_branch( i=true, &blk )
    enum = Enumerator.new do |y|
      y.yield self if i
      @children.each do |c|
        next unless c.is_a? Tree
        y.yield c
        c.each_branch( false ).each {|b| y.yield b }
      end
    end
    block_given? ? enum.each( &blk ) : enum
  end

  # This yields each leaf and its parent.
  def each_leaf( parent=self, &blk )
    enum = Enumerator.new do |y|
      @children.each do |c|
        if !c.leaf?
          c.each_leaf( c ).each do |l,p|
            y.yield l, p
          end
        else y.yield c, parent
        end
      end
    end
    block_given? ? enum.each( &blk ) : enum
  end

end

我只是从我之前制作的树结构中借用了这些枚举器 - each_leaf 方法也可能有帮助,您可以检查该类是否不是 Tree 而不是 leaf? 返回 true 如果您有一个可以包含其他内容的树结构对象,如字符串)。

然后你可以这样做:

root_tree = Tree.new "Farming", "farming"
root_tree << Tree.new( "Organic Gas", "organic_gas" )

gas = root_tree.find "Organic gas"
gas << Tree.new(...)

我认为这只是为工作找到正确的数据结构的一个例子。即使树方法的效率稍低,它也会更清楚发生的情况,并且可能会导致动态代码中的错误更少。

如果问题是您不想修改原始树,只想复制,然后重新定义:

class Tree
  attr_accessor :children

  def <<(child)
    new_tree = self.dup
    new_tree.children = @children + [child]
    new_tree
  end
end

这样,您保留原始树,但返回添加了额外子项的新树。

I'm not sure a Hash is really the best data structure here. You're trying to use it to represent a tree, which is fine and all, but it might be a bit clearer if you just explicitly made it a tree:

class Tree
  attr_reader :name, :uri, :children, :parent

  def initialize(name, uri, *children)
    @children = children
    @name, @uri = name, uri
  end

  def <<(child)
    @children << child
  end

  def find(name)
    each_branch.detect {|branch| branch.name == name }
  end

  def leaf?
    @children.empty?
  end

  # The parameter `i` just decides whether or not to include self.
  def each_branch( i=true, &blk )
    enum = Enumerator.new do |y|
      y.yield self if i
      @children.each do |c|
        next unless c.is_a? Tree
        y.yield c
        c.each_branch( false ).each {|b| y.yield b }
      end
    end
    block_given? ? enum.each( &blk ) : enum
  end

  # This yields each leaf and its parent.
  def each_leaf( parent=self, &blk )
    enum = Enumerator.new do |y|
      @children.each do |c|
        if !c.leaf?
          c.each_leaf( c ).each do |l,p|
            y.yield l, p
          end
        else y.yield c, parent
        end
      end
    end
    block_given? ? enum.each( &blk ) : enum
  end

end

(I just borrowed those enumerators from a tree structure I'd made before - the each_leaf method might be helpful too, and you can check for the class not being Tree instead of leaf? returning true if you had a tree structure that could contain other objects, like strings).

Then you could do:

root_tree = Tree.new "Farming", "farming"
root_tree << Tree.new( "Organic Gas", "organic_gas" )

gas = root_tree.find "Organic gas"
gas << Tree.new(...)

I think this is just a case of finding the right data structure for the job. Even if the tree method were a bit less efficient, it's clearer what's going on, and will probably lead to fewer bugs in your dynamic code down the track.

If the problem is that you don't want the original tree to be modified, only copied, then just redefine:

class Tree
  attr_accessor :children

  def <<(child)
    new_tree = self.dup
    new_tree.children = @children + [child]
    new_tree
  end
end

That way, you retain the original tree but return a new tree with the extra child added.

丶情人眼里出诗心の 2024-12-15 12:41:19

要回答最初的问题,您可以使用 XKeys gem 遍历动态路径,如下所示:

require 'xkeys' # on rubygems.org

main_hash.extend XKeys::Hash
path = ['Farming', 'children', 'Organic Gas', 'children']
main_hash[*path].merge!(another_hash)

我还建议使用 :children 而不是 'children' 以避免出现大量重复字符串。

To answer the original question, you can use the XKeys gem to traverse a dynamic path as follows:

require 'xkeys' # on rubygems.org

main_hash.extend XKeys::Hash
path = ['Farming', 'children', 'Organic Gas', 'children']
main_hash[*path].merge!(another_hash)

I would also recommend using :children instead of 'children' to avoid having huge numbers of duplicate strings.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文