我正在用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

最佳答案

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实现允许您在数组中重复散列,如果一个面额与其初始值amount不同,则可能导致错误您只需使用哈希将面额和金额存储为键值:
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}

那是Ruby2.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}]

10-07 12:25