计算两个不同哈希值的总计

时间:2017-03-26 09:59:22

标签: ruby hash lookup

我有两个哈希:

例如,一个包含菜肴清单及其价格

dishes = {"Chicken"=>12.5, "Pizza"=>10, "Pasta"=>8.99}

另一个是篮子哈希,即我选择了一个意大利面和两个披萨:

basket = {"Pasta"=>1, "Pizza"=>2}

现在我正在尝试计算购物篮的总成本,但似乎无法使我的参考文献正确。

尝试了

basket.inject { |item, q| dishes[item] * q }

但不断收到以下错误

  

NoMethodError:未定义的方法`*'为零:NilClass

4 个答案:

答案 0 :(得分:2)

basket.inject { |item, q| dishes[item] * q }

让我们看看Enumerable#inject的文档,看看发生了什么。 inject通过获取“起始对象”然后将二进制操作重复应用于起始对象和第一个元素,然后将结果与第二个元素相关联,然后将该集合“折叠”到单个对象中,然后到那个和第三个元素的结果,等等。

因此,块接收两个参数:累加器的当前值和当前元素,并且该块返回累加器的新值以用于下一次调用块。如果没有为累加器提供起始值,则使用该集合的第一个元素。

因此,在这里的第一次迭代中,由于您没有为累加器提供起始值,因此该值将成为第一个元素;迭代将从第二个元素开始。这意味着在第一次迭代期间,item将为['Pasta', 1]q将为['Pizza', 2]。让我们来看看我们头脑中的例子:

dishes[item] * q                    # item is ['Pasta', 1]
dishes[['Pasta', 1]] * q            # q is ['Pizza', 2]
dishes[['Pasta', 1]] * ['Pizza', 2] # there is no key ['Pasta', 1] in dishes
nil * ['Pizza', 2]                  # nil doesn't have * method

因此,你得到NoMethodError

现在,我相信,你实际想做的事情是这样的:

basket.inject(0.0) {|sum, (item, q)| sum + dishes[item] * q }
#             ↑↑↑    ↑↑↑             ↑↑↑↑↑
  • 你不想累积订单,你想累积数字,所以你需要提供一个数字作为起始值;如果不这样做,则起始值将是第一个元素,即订单,而不是数字
  • 您混淆了块参数的含义
  • 你实际上并没有总结任何事情

现在,虽然inject能够求和(事实上,inject能够任何,但它是通用迭代操作,即你可以用循环做的任何事情,你也可以用inject),如果它们存在,通常最好使用更专业的操作。在这种情况下,存在一个用于求和的更专业的操作,它被称为Enumerable#sum

basket.sum {|item, q| dishes[item] * q }

但是你的代码存在更深层次的问题:Ruby是一种面向对象的语言。它不是一个以字符串和浮点数为主的语言数组。您应该构建表示域抽象的对象:

class Dish < Struct.new(:name, :price)
  def to_s; "#{name}: $#{price}" end
  def *(num) num * price end
  def coerce(other) [other, price] end
end

require 'bigdecimal'
require 'bigdecimal/util'

dishes = {
  chicken: Dish.new('Chicken', '12.5'.to_d), 
  pizza: Dish.new('Pizza', '10'.to_d),
  pasta: Dish.new('Pasta', '8.99'.to_d)
}

class Order < Struct.new(:dish, :quantity)
  def to_s; "#{quantity} * #{dish}" end
  def total; quantity * dish end
end

class Basket
  def initialize(*orders)
    self.orders = orders
  end

  def <<(order)
    orders << order
  end

  def to_s; orders.join("\n") end

  def total; orders.sum(&:total) end

  private

  attr_accessor :orders
end

basket = Basket.new(
  Order.new(dishes[:pasta], 1), 
  Order.new(dishes[:pizza], 2)
)

basket.total
#=> 0.2899e2

现在,当然,对于这样一个简单的例子,这是过度的。但我希望你能看到,尽管这是更多的代码,但它也简单得多。复杂的嵌套结构有复杂的导航,因为a)没有复杂的嵌套结构,b)所有对象都知道如何处理自己,从来没有必要“拆开”一个对象来检查它的部分和对它们进行复杂的计算,因为对象本身知道它们自己的部分以及如何对它们进行计算。

注意:就个人而言,我认为允许对Dish es进行算术运算是一个好主意。我想在这个代码片段中展示它更像是一个“整洁的黑客”。

答案 1 :(得分:1)

使用Ruby 2.4,您可以将Hash(Enumerable)#sum与块一起使用:

basket = {"Pasta"=>1, "Pizza"=>2}
prices = {"Chicken"=>12.5, "Pizza"=>10, "Pasta"=>8.99}

basket.sum{ |dish, quantity| quantity * prices[dish] }
# 28.99

数据结构

dishes(我称之为prices以避免编写dishes[dish])是正确的数据结构:

  • 哈希查找很快
  • 如果您想更新菜肴的价格,只需在一个地方进行
  • 它基本上是一个迷你数据库。

basket作为哈希也很好,但前提是你不要再吃任何一道菜。如果您想再订购2个比萨饼,1个意大利面和3个比萨饼:

{"Pizza"=>2, "Pasta" => 1, "Pizza" =>3}
=> {"Pizza"=>3, "Pasta"=>1}

你将失去第一笔订单。

在这种情况下,您可能希望使用对数组(带有dishquantity的2元素数组):

basket = [["Pizza", 2], ["Pasta", 1], ["Pizza", 3]]

使用这种结构,您可以使用完全相同的语法来获得总数与哈希:

basket.sum{ |dish, quantity| quantity * prices[dish] }

答案 2 :(得分:0)

试试这个

basket.inject(0) do |acc, item|
  dish, q = item
  acc + (dishes[dish] * q)
end
 => 28.990000000000002 

一行

basket.inject(0) { |acc, item| acc + (dishes[item.first] * item.last) }

块的变量是错误的。你有累加器和一个项目(它是一个哈希)

答案 3 :(得分:0)

2.2.0 :011 > basket.inject(0){ |sum, (item, q)| sum + dishes[item].to_f * q }
=> 28.990000000000002