Как я могу рекурсивно определить Hash в Ruby из предоставленных аргументов? - PullRequest
3 голосов
/ 25 мая 2010

Этот фрагмент кода заполняет хэш @options. values - это Array, который содержит ноль или более разнородных элементов. Если вы вызываете populate с аргументами, которые являются Hash записями, он использует значение, указанное вами для каждой записи, чтобы принять значение по умолчанию.

def populate(*args)
  args.each do |a|
    values = nil
    if (a.kind_of? Hash)
      # Converts {:k => "v"} to `a = :k, values = "v"`
      a, values = a.to_a.first
    end

    @options[:"#{a}"] ||= values ||= {}
  end
end

Я хотел бы изменить populate так, чтобы оно рекурсивно заполняло @options. Существует особый случай: если значения, которые он собирается заполнить ключом, представляют собой массив, состоящий полностью из (1) символов или (2) хэшей, ключи которых являются символами (или некоторой комбинацией двух), то их следует рассматривать как подразделы , а не значения, связанные с этим ключом, и та же логика, используемая для оценки исходных аргументов populate, должна быть рекурсивно применена повторно.

Это было немного сложно выразить словами, поэтому я написал несколько тестов. Вот несколько тестов и ожидаемое значение @options после:

populate :a
=> @options is {:a => {}}

populate :a => 42
=> @options is {:a => 42}

populate :a, :b, :c
=> @options is {:a => {}, :b => {}, :c => {}}

populate :a, :b => "apples", :c
=> @options is {:a => {}, :b => "apples", :c => {}}

populate :a => :b
=> @options is {:a => :b}

# Because [:b] is an Array consisting entirely of Symbols or
# Hashes whose keys are Symbols, we assume that :b is a subkey
# of @options[:a], rather than the value for @options[:a].
populate :a => [:b]
=> @options is {:a => {:b => {}}}

populate :a => [:b, :c => :d]
=> @options is {:a => {:b => {}, :c => :d}}

populate :a => [:a, :b, :c]
=> @options is {:a => {:a => {}, :b => {}, :c => {}}}

populate :a => [:a, :b, "c"]
=> @options is {:a => [:a, :b, "c"]}

populate :a => [:one], :b => [:two, :three => "four"]
=> @options is {:a => :one, :b => {:two => {}, :three => "four"}}

populate :a => [:one], :b => [:two => {:four => :five}, :three => "four"]
=> @options is {:a => :one,
                :b => {
                   :two => {
                      :four => :five
                      }
                   },
                   :three => "four"
                }
               }

Допустимо, если сигнатура populate должна измениться, чтобы приспособиться к какой-то рекурсивной версии. Нет предела количеству вложений, которое теоретически может произойти.

Есть мысли о том, как я могу это осуществить?

Ответы [ 2 ]

1 голос
/ 29 июля 2010

Ruby не является Perl, => работает только внутри реального определения Hash или в качестве последнего аргумента при вызове метода. Большинство вещей, которые вы хотите, приведет к синтаксической ошибке.

Вы уверены, что populate ограничено случаями, поддерживаемыми синтаксисом Ruby, того стоит?

1 голос
/ 26 мая 2010

Итак, вот простой код, который работает.

def to_value args
  ret = {}
  # make sure we were given an array
  raise unless args.class == Array
  args.each do |arg|
    case arg
    when Symbol
      ret[arg] = {} 
    when Hash
      arg.each do |k,v|
        # make sure that all the hash keys are symbols
        raise unless k.class == Symbol
        ret[k] = to_value v 
      end           
    else    
      # make sure all the array elements are symbols or symbol-keyed hashes
      raise         
    end     
  end
  ret
rescue
  args
end
def populate *args
  @options ||= {}
  value = to_value(args)
  if value.class == Hash
    @options.merge! value
  end
end

Это отклоняется от ваших тестовых случаев:

  • контрольный пример populate :a, :b => "apples", :c - синтаксическая ошибка ruby. Ruby примет, что аргумент final для метода является хешем (если не даны фигурные скобки), но не является окончательным, как вы предполагаете здесь. Данный код является синтаксической ошибкой (независимо от определения populate), поскольку он предполагает, что :c является хеш-ключом, и находит конец строки, когда ищет значение :c. populate :a, {:b => "apples"}, :c работает как положено
  • контрольный пример populate :a => [:one], :b => [:two, :three => "four"] возвращает {:a=>{:one=>{}}, :b=>{:two=>{}, :three=>"four"}}. Это согласуется с контрольным примером populate :a => [:b].
...