简体   繁体   English

如何从提供的参数递归定义Ruby中的Hash?

[英]How do I recursively define a Hash in Ruby from supplied arguments?

This snippet of code populates an @options hash. 这段代码填充了@options哈希。 values is an Array which contains zero or more heterogeneous items. values是一个包含零个或多个异构项的Array If you invoke populate with arguments that are Hash entries, it uses the value you specify for each entry to assume a default value. 如果使用作为Hash条目的参数调用populate ,它将使用您为每个条目指定的值来采用默认值。

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

What I'd like to do is change populate such that it recursively populates @options . 我想做的是改变populate ,以便递归填充@options There is a special case: if the values it's about to populate a key with are an Array consisting entirely of (1) Symbols or (2) Hashes whose keys are Symbols (or some combination of the two), then they should be treated as subkeys rather than the values associated with that key, and the same logic used to evaluate the original populate arguments should be recursively re-applied. 有一种特殊情况:如果要填充键的值是一个完全由(1)符号组成的数组或(2)键是符号(或两者的某种组合)的哈希值,那么它们应该被视为子键而不是与该键关联的值,并且应该递归地重新应用用于评估原始populate参数的相同逻辑。

That was a little hard to put into words, so I've written some test cases. 这有点难以言喻,所以我写了一些测试用例。 Here are some test cases and the expected value of @options afterwards: 以下是一些测试用例和@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"
                }
               }

It is acceptable if the signature of populate needs to change to accommodate some kind of recursive version. 如果populate的签名需要改变以适应某种递归版本是可以接受的。 There is no limit to the amount of nesting that could theoretically happen. 理论上可以发生的嵌套量没有限制。

Any thoughts on how I might pull this off? 关于我如何解决这个问题的任何想法?

So here's some simple code that works. 所以这里有一些简单的代码可行。

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

It does deviate from your test cases: 它确实偏离了您的测试用例:

  • test case populate :a, :b => "apples", :c is a ruby syntax error. 测试用例populate :a, :b => "apples", :c是ruby语法错误。 Ruby will assume the final argument to a method is a hash (when not given braces), but not a non-final one, as you assume here. Ruby会假设方法的最后一个参数是一个哈希(当没有给出大括号时),但不是非最终的,正如你在这里假设的那样。 The given code is a syntax error (no matter the definition of populate ) since it assumes :c is a hash key, and finds an end of line when it's looking for :c 's value. 给定的代码是语法错误(无论populate的定义),因为它假定:c是一个散列键,并在查找时找到行尾:c的值。 populate :a, {:b => "apples"}, :c works as expected populate :a, {:b => "apples"}, :c按预期工作
  • test case populate :a => [:one], :b => [:two, :three => "four"] returns {:a=>{:one=>{}}, :b=>{:two=>{}, :three=>"four"}} . 测试用例populate :a => [:one], :b => [:two, :three => "four"]返回{:a=>{:one=>{}}, :b=>{:two=>{}, :three=>"four"}} This is consistent with the test case populate :a => [:b] . 这与populate :a => [:b]测试用例一致populate :a => [:b]

Ruby isn't Perl, => works only inside real Hash definition or as final argument in method call. Ruby不是Perl, =>仅在真正的Hash定义内部工作或在方法调用中作为最终参数。 Most things you want will result in a syntax error. 您想要的大多数事情都会导致语法错误。

Are you sure that populate limited to cases supported by Ruby syntax is worth it? 您确定仅限于Ruby语法支持的populate值是否值得?

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM