计算来自两个不同哈希的总数 [英] Calculating totals from two different hashes
问题描述
我有两个哈希值:
例如,其中包含一道菜及其价格列表
For example, one contains a list of dishes and their prices
dishes = {"Chicken"=>12.5, "Pizza"=>10, "Pasta"=>8.99}
另一个是篮子杂菜,即我选择了一个面食和两个披萨:
The other is a basket hash i.e. I've selected one pasta and two pizzas:
basket = {"Pasta"=>1, "Pizza"=>2}
现在,我正在尝试计算购物篮的总费用,但似乎无法正确地获得我的推荐信.
Now I am trying to calculate the total cost of the basket but can't seem to get my references right.
尝试过
basket.inject { |item, q| dishes[item] * q }
但是继续出现以下错误
NoMethodError:nil:NilClass的未定义方法'*'
NoMethodError: undefined method `*' for nil:NilClass
推荐答案
basket.inject { |item, q| dishes[item] * q }
让我们看一下 Enumerable#inject
的文档看看发生了什么. inject
通过采用起始对象",然后将二进制操作重复应用于起始对象和第一个元素,然后对该对象和第二个元素的结果,再对对象进行折叠",将集合折叠为单个对象. that 和第三个元素的结果,等等.
Let's look at the documentation for Enumerable#inject
to see what is going on. inject
"folds" the collection into a single object, by taking a "starting object" and then repeatedly applying the binary operation to the starting object and the first element, then to the result of that and the second element, then to the result of that and the third element, and so forth.
因此,该块接收两个参数:累加器的当前值和当前元素,并且该块返回该累加器的新值,以供下一次调用该块.如果不为累加器提供起始值,则使用集合的第一个元素.
So, the block receives two arguments: the current value of the accumulator and the current element, and the block returns the new value of the accumulator for the next invocation of the block. If you don't supply a starting value for the accumulator, then the first element of the collection is used.
因此,在这里的第一次迭代中,由于您没有为累加器提供起始值,因此该值将成为第一个元素;迭代将从第二个元素开始.这意味着在第一次迭代期间,item
将是['Pasta', 1]
,而q
将是['Pizza', 2]
.让我们来看一下示例:
So, during the first iteration here, since you didn't supply a starting value for the accumulator, the value is going to be the first element; and iteration is going to start from the second element. This means that during the first iteration, item
is going to be ['Pasta', 1]
and q
is going to be ['Pizza', 2]
. Let's just run through the example in our heads:
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
Ergo,您得到一个NoMethodError
.
Ergo, you get a NoMethodError
.
现在,我相信,您实际上想要做的是这样的事情:
Now, I believe, what you actually wanted to do was something like this:
basket.inject(0.0) {|sum, (item, q)| sum + dishes[item] * q }
# ↑↑↑ ↑↑↑ ↑↑↑↑↑
- 您不想累积订单,想要累积数字,因此您需要提供一个数字作为起始值;如果不这样做,则起始值将是第一个元素,这是一个顺序,而不是数字
- 您混淆了块参数的含义
- 您实际上不是求和任何东西
- You don't want to accumulate orders, you want to accumulate numbers, so you need to supply a number as the starting value; if you don't, the starting value will be the first element, which is an order, not a number
- You were mixing up the meaning of the block parameters
- You weren't actually summing anything
现在,虽然inject
可以求和(实际上,inject
可以任何东西,但这是 general 迭代操作,即您可以执行的所有操作)使用循环,也可以使用inject
),通常最好使用更专门的操作(如果存在).在这种情况下,存在一种用于对做求和的更专门的操作,它被称为
Now, while inject
is capable of summing (in fact, inject
is capable of anything, it is a general iteration operation, i.e. anything you could do with a loop, you can also do with inject
), it is usually better to use more specialized operations if they exist. In this case, a more specialized operation for summing does exist, and it is called Enumerable#sum
:
basket.sum {|item, q| dishes[item] * q }
但是您的代码存在一个更深层的潜在问题:Ruby是一种面向对象的语言.它不是面向字符串和浮点数的数组的语言.您应该构建代表域抽象的对象:
But there is a deeper underlying problem with your code: Ruby is an object-oriented language. It is not an array-of-hash-of-strings-and-floats-oriented language. You should build objects that represent your domain abstractions:
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)所有对象都知道如何照顾自己,因此无需分解"对象来检查其各个部分,在对象上运行复杂的计算,因为对象本身知道它们自己的部分以及如何在它们上运行计算.
Now, of course, for such a simple example, this is overkill. But I hope that you can see that despite this being more code, it is also much much simpler. There is complex navigation of complex nested structures, because a) there are no complex nested structures and b) all the objects know for how to take care of themselves, there is never a need to "take apart" an object to examine its parts and run complex calculations on them, because the objects themselves know their own parts and how to run calculations on them.
注意:就我个人而言,我不是认为允许在Dish
es上进行算术运算是一个好主意.我更想在此代码段中炫耀这种精巧的技巧".
Note: personally, I do not think that allowing arithmetic operations on Dish
es is a good idea. It is more of a "neat hack" that I wanted to show off in this code snippet.
这篇关于计算来自两个不同哈希的总数的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!