0

我正在尝试从本地环境中定义的哈希中返回基于用户定义参数的值列表。

def my_method *args
  #initialize accumulator
  accumulator = Hash.new(0)

  #define hashes in local environment
  foo=Hash["key1"=>["var1","var2"],"key2"=>["var3","var4","var5"]]
  bar=Hash["key3"=>["var6"],"key4"=>["var7","var8","var9"],"key5"=>["var10","var11","var12"]]
  baz=Hash["key6"=>["var13","var14","var15","var16"]]

  #iterate over args and build accumulator
  args.each do |x|
    if foo.has_key?(x)
        accumulator=foo.assoc(x)
    elsif bar.has_key?(x)
        accumulator=bar.assoc(x)
    elsif baz.has_key?(x)
        accumulator=baz.assoc(x)
    else
        puts "invalid input"
    end
end

  #convert accumulator to list, and return value
  return accumulator = accumulator.to_a {|k,v| [k].product(v).flatten}
end

用户将使用作为关键字的参数调用该方法,并且该函数将返回与接收到的每个关键字相关联的值列表。

例如

> my_method(key5,key6,key1)
=> ["var10","var11","var12","var13","var14","var15","var16","var1","var2"]

输出可以是任何顺序。当我尝试运行代码时收到以下错误:

undefined method `assoc' for #<Hash:0x10f591518> (NoMethodError)

请你指点我如何解决这个问题?在终端assoc中完全按照我的预期执行:

> foo.assoc("key1")
=> ["var1","var2"]
4

1 回答 1

1

我猜你是从其他语言开始使用 Ruby 的,因为这种方法有很多不必要的麻烦。此外,由于各种原因,它不会返回您期望的结果。

`accumulator = Hash.new(0)`

这是不必要的,因为 (1),您期望返回一个数组,并且 (2),您不需要在 ruby​​ 中预先初始化变量。

在这种Hash[...]情况下,语法是非常规的,通常用于将其他一些可枚举(通常是数组)转换为哈希,如Hash[1,2,3,4] #=> { 1 => 2, 3 => 4}. 当你定义一个哈希时,你可以只使用花括号{ ... }

对于 的每次迭代args,您都将累加器分配给哈希查找的结果,而不是累加值(根据您的示例输出,这是您需要做的)。相反,您应该查看各种数组连接方法,如push, +=,<<等。

看起来您不需要结果中的密钥,这assoc可能是矫枉过正。fetch使用简单的括号查找 ( )会更好hash[key]

最后,虽然您可以使用块调用 Ruby 中的任何方法,就像您对 所做的那样to_a,除非该方法专门为块生成值,否则 Ruby 将忽略它,因此[k].product(v).flatten实际上并没有做任何事情。

我并不是说太挑剔——Ruby 的语法非常灵活,但与其他语言相比也相对紧凑,这意味着它很容易走得太远,最终导致难以理解和难以维护的方法。

您的方法的构造方式还有另一个副作用,其中累加器只会从具有特定键的第一个散列中收集值,即使多个散列具有该键也是如此。由于我不知道这是否是故意的,我将保留此功能。

这是您的方法的一个版本,它返回您所期望的:

def my_method(*args)
  foo = { "key1"=>["var1","var2"],"key2"=>["var3","var4","var5"] }
  bar = { "key3"=>["var6"],"key4"=>["var7","var8","var9"],"key5"=>["var10","var11","var12"] }
  baz = { "key6"=>["var13","var14","var15","var16"] }

  merged = [foo, bar, baz].reverse.inject({}, :merge)

  args.inject([]) do |array, key|
    array += Array(merged[key])
  end
end

一般来说,我不会定义带有内置数据的方法,但我会保留它以更接近您的原始方法。Hash#merge组合两个散列并用参数散列中的那些覆盖原始散列中的任何重复键。即使键不存在,该Array()调用也会强制一个数组,因此您不需要显式处理该错误。

我鼓励您查看该inject方法 - 它非常通用,在许多情况下都很有用。inject使用它自己的累加器变量(可选地定义为参数)作为第一个块参数产生给块。

于 2013-03-31T09:01:52.553 回答