从两个不同的哈希计算总数
Calculating totals from two different hashes
我有两个哈希:
例如,一个包含菜肴及其价格的列表
dishes = {"Chicken"=>12.5, "Pizza"=>10, "Pasta"=>8.99}
另一个是篮子哈希,即我选择了一个意大利面和两个比萨饼:
basket = {"Pasta"=>1, "Pizza"=>2}
现在我正在尝试计算篮子的总成本,但似乎无法获得正确的参考。
尝试过
basket.inject { |item, q| dishes[item] * q }
但不断出现以下错误
NoMethodError: undefined method `*' for nil:NilClass
试试这个
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) }
你的块变量是错误的。你有累加器和一个项目(它是一个散列)
在 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.2.0 :011 > basket.inject(0){ |sum, (item, q)| sum + dishes[item].to_f * q }
=> 28.990000000000002
basket.inject { |item, q| dishes[item] * q }
让我们看看 Enumerable#inject
的文档,看看发生了什么。 inject
将集合“折叠”为单个对象,方法是获取“起始对象”,然后将二元运算重复应用于起始对象和第一个元素,然后应用于该对象和第二个元素的结果,然后that 和第三个元素的结果,依此类推。
因此,块接收两个参数:累加器的当前值和当前元素,块 returns 块的下一次调用的累加器的新值。如果您没有为累加器提供起始值,则使用集合的第一个元素。
因此,在这里的第一次迭代期间,由于您没有为累加器提供起始值,因此该值将成为第一个元素;迭代将从第二个元素开始。这意味着在第一次迭代期间,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
),如果它们存在,通常最好使用更专门的操作。在这种情况下,存在一个更专门的求和运算 does,它被称为 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进行算术运算是个好主意。我想在此代码片段中展示它更像是一个“巧妙的 hack”。
我有两个哈希:
例如,一个包含菜肴及其价格的列表
dishes = {"Chicken"=>12.5, "Pizza"=>10, "Pasta"=>8.99}
另一个是篮子哈希,即我选择了一个意大利面和两个比萨饼:
basket = {"Pasta"=>1, "Pizza"=>2}
现在我正在尝试计算篮子的总成本,但似乎无法获得正确的参考。
尝试过
basket.inject { |item, q| dishes[item] * q }
但不断出现以下错误
NoMethodError: undefined method `*' for nil:NilClass
试试这个
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) }
你的块变量是错误的。你有累加器和一个项目(它是一个散列)
在 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.2.0 :011 > basket.inject(0){ |sum, (item, q)| sum + dishes[item].to_f * q }
=> 28.990000000000002
basket.inject { |item, q| dishes[item] * q }
让我们看看 Enumerable#inject
的文档,看看发生了什么。 inject
将集合“折叠”为单个对象,方法是获取“起始对象”,然后将二元运算重复应用于起始对象和第一个元素,然后应用于该对象和第二个元素的结果,然后that 和第三个元素的结果,依此类推。
因此,块接收两个参数:累加器的当前值和当前元素,块 returns 块的下一次调用的累加器的新值。如果您没有为累加器提供起始值,则使用集合的第一个元素。
因此,在这里的第一次迭代期间,由于您没有为累加器提供起始值,因此该值将成为第一个元素;迭代将从第二个元素开始。这意味着在第一次迭代期间,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
),如果它们存在,通常最好使用更专门的操作。在这种情况下,存在一个更专门的求和运算 does,它被称为 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进行算术运算是个好主意。我想在此代码片段中展示它更像是一个“巧妙的 hack”。