Рубиновый вложенный хэш, извлекаемый динамически - PullRequest
1 голос
/ 06 октября 2011

У меня проблема с попыткой обновить вложенный хэш с помощью ключа.

Вложенный мной хэш выглядит так:

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")

Было бы действительно здорово, если бы я мог вызывать метод "send" динамически, какниже (очевидно, это не сработает).

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

Надеюсь, это прояснит, чего я хочу достичь.Я прошел через все встроенные функции Ruby Hash, и я не могу найти ту, которая подходит для моих нужд.

Любая помощь будет принята с благодарностью.

Приветствия.

Ответы [ 2 ]

4 голосов
/ 06 октября 2011

Я не уверен, что 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 вместо 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

Таким образом, вы сохраняете исходное дерево, но возвращаете новое дерево с добавленным дополнительным потомком.

2 голосов
/ 26 июля 2013

Чтобы ответить на исходный вопрос, вы можете использовать гем XKeys для обхода динамического пути следующим образом:

require 'xkeys' # on rubygems.org

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

Я бы также рекомендовал использовать: children вместо 'children', чтобы избежать огромного количестваповторяющиеся строки.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...