Ruby嵌套哈希动态获取

时间:2011-10-05 21:07:08

标签: ruby dynamic hash nested fetch

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

我拥有的嵌套哈希是这样的:

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的内置功能,但我无法获得适合我需要的功能。

非常感谢任何帮助。

干杯。

2 个答案:

答案 0 :(得分:4)

我不确定Hash是否真的是最好的数据结构。你试图用它来代表一棵树,这很好,但是如果你明确地把它变成一棵树,它可能会更清楚一些:

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而不是{{1如果您的树结构可以包含其他对象(如字符串),则返回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(...)

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

答案 1 :(得分:2)

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

require 'xkeys' # on rubygems.org

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

我还建议使用:孩子而不是'孩子',以避免有大量重复的字符串。