尝试访问Ruby哈希时出现NoMethodError,但在irb中有效

时间:2019-07-07 22:04:38

标签: ruby hash

我正在Ruby中建立一个具有以下哈希值的收银机:



    @change = [
                  { :denomination => 0.01, :amount => 5 },
                  { :denomination => 0.02, :amount => 5 },
                  { :denomination => 0.05, :amount => 5 },
                  { :denomination => 0.10, :amount => 5 },
                  { :denomination => 0.20, :amount => 5 },
                  { :denomination => 0.50, :amount => 5 },
                  { :denomination => 1.00, :amount => 5 },
                  { :denomination => 2.00, :amount => 5 }
        ]

用户进行购买时,他们使用的硬币以数组形式作为参数传递。


    def pay(coins = [])
        coins.each do |coin|
          coin = @change.find { |x| x[:denomination] == coin }
          coin[:amount] += 1
        end
     end

当我从终端手动在irb中进行测试时,此方法工作正常。但是,当我尝试运行rspec时,它失败并显示NoMethodError:

 Failure/Error: coin[:amount] += 1

     NoMethodError:
       undefined method `[]' for nil:NilClass

我真的看不到为什么出现NoMethodError失败,尤其是在终端中运行正常时。谁能帮忙吗?

谢谢:)

更新:

规范:

describe '#pay' do
    it 'updates the change' do
      till.pay([0.5])
      expect(till.change).to include(:denomination=>0.5, :amount=>6)
    end
  end

1 个答案:

答案 0 :(得分:3)

nil对象中调用[]时出错。

如果传递的面额不在@change实例变量中,则@change.find { ... }的结果将为nil,并在[]中调用nil对象将引发NoMethodError。

这种情况您还没有涉及,可以通过在next枚举器中使用each来解决:

coins.each do |coin|
  current_denomination = @change.find { |x| x[:denomination] == coin }
  next unless current_denomination

  current_denomination[:amount] += 1
end

p pay([0.05, 0.05, 2.0, 100])

如果相同的硬币和面额没有变化,您只需“跳”到硬币中的下一个元素。

请注意,@change的当前实现允许您重复数组中的哈希值,如果面额与其数量的初始值不同,则可能导致错误。您可以只使用哈希将面额和金额都存储为键值:

p [0.01, 0.02, 0.05, 0.1, 0.2, 0.5, 1.0, 2.0].to_h { |a| [a, 5] }
# {0.01=>5, 0.02=>5, 0.05=>5, 0.1=>5, 0.2=>5, 0.5=>5, 1.0=>5, 2.0=>5}

那是Ruby 2.6 +。


作为实现的一个细微调整,您可以将@change移到一个单独的方法上,然后在其上调用tap来调用和使用该方法:

DENOMINATIONS = [0.01, 0.02, 0.05, 0.1, 0.2, 0.5, 1.0, 2.0].freeze
HASH_KEYS = %i[denomination amount].freeze

def initial_coins
  DENOMINATIONS.zip(Array.new(8, 5)).map { |coin| HASH_KEYS.zip(coin).to_h }
end

def pay(coins = [])
  initial_coins.tap do |this|
    coins.each do |coin|
      current_denomination = this.find { |initial_coin| initial_coin[:denomination] == coin }
      next unless current_denomination

      current_denomination[:amount] += 1
    end
  end
end

pp pay([0.05, 0.05, 2.0, 100])
# [{:denomination=>0.01, :amount=>5},
#  {:denomination=>0.02, :amount=>5},
#  {:denomination=>0.05, :amount=>7},
#  {:denomination=>0.1, :amount=>5},
#  {:denomination=>0.2, :amount=>5},
#  {:denomination=>0.5, :amount=>5},
#  {:denomination=>1.0, :amount=>5},
#  {:denomination=>2.0, :amount=>6}]