如何在Ruby中从提供的参数递归定义哈希?

3

这段代码填充一个@options哈希表。 values是一个包含零个或多个异构项的数组。如果你使用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

我想要做的是更改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

这里有一些简单的代码可以运行。

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会假定方法的最后一个参数是哈希表(非最后一个则不是),而不是像你在这里假定的那样。给定的代码是一个语法错误(无论populate的定义如何),因为它假定:c是一个哈希键,并在寻找:c的值时找到了一行的结尾。populate :a, {:b => "apples"}, :c按预期工作。
  • 测试用例populate :a => [:one], :b => [:two, :three => "four"]返回{:a=>{:one=>{}}, :b=>{:two=>{}, :three=>"four"}}。这与测试用例populate :a => [:b]一致。

1

Ruby不是Perl,=>仅在真正的哈希定义内部或作为方法调用中的最终参数中起作用。大多数你想要的东西都会导致语法错误。

你确定只限于Ruby语法支持的情况下使用populate值得吗?


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接