【问题标题】:undefined method `assoc' for #<Hash:0x10f591518> (NoMethodError)#<Hash:0x10f591518> 的未定义方法 `assoc' (NoMethodError)
【发布时间】:2013-03-21 14:11:16
【问题描述】:

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

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

【问题讨论】:

  • 什么版本的 Ruby? Hash#assoc 是在 Ruby 1.9 中添加的。它在 Ruby 1.8 中不可用。
  • 1.9.3 抱歉我错过了
  • 也许 assoc 位于控制台 irb 加载的另一个 gem 中,但在您运行脚本时却没有。
  • 这是一个标准的Hash方法,应该是内置的

标签: ruby hash enumerable


【解决方案1】:

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

`accumulator = Hash.new(0)`

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

Hash[...] 语法在这种情况下是非常规的,通常用于将其他一些可枚举(通常是数组)转换为散列,如Hash[1,2,3,4] #=&gt; { 1 =&gt; 2, 3 =&gt; 4}。定义哈希时,只需使用大括号 { ... }

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

由于看起来您不需要结果中的键,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 使用它自己的累加器变量(可选地定义为参数)作为第一个块参数产生给块。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-04-13
    • 1970-01-01
    • 1970-01-01
    • 2013-04-24
    • 2015-05-19
    • 1970-01-01
    相关资源
    最近更新 更多