我有两个哈希:
例如,一个包含菜肴清单及其价格
dishes = {"Chicken"=>12.5, "Pizza"=>10, "Pasta"=>8.99}
另一个是篮子哈希,即我选择了一个意大利面和两个披萨:
basket = {"Pasta"=>1, "Pizza"=>2}
现在我正在尝试计算购物篮的总成本,但似乎无法使我的参考文献正确。
尝试了
basket.inject { |item, q| dishes[item] * q }
但不断收到以下错误
NoMethodError:未定义的方法`*'为零:NilClass
答案 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}
你将失去第一笔订单。
在这种情况下,您可能希望使用对数组(带有dish
和quantity
的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