我正在努力理解Rubymonk编码练习解决方案,并且无法理解cost
方法中发生的事情。
作为参考,menu
为{:rice => 3, :noodles => 2}
,其目的是计算menu
的订单总费用。
订单示例如下:
{:rice => 1, :noodles => 1} )
我提出的解决方案更简单,至少在我脑海中,但返回了“无法将符号转换为整数”错误,我无法通过to_i
纠正错误。
class Restaurant
def initialize(menu)
@menu = menu
end
def cost(*orders)
orders.inject(0) do |total_cost, order|
total_cost + order.keys.inject(0) {|cost, key| cost + @menu[key]*order[key] }
end
end
end
有人可以简单解释cost
方法中的每一步吗?
答案 0 :(得分:4)
考虑到正在计算total cost
,@menu
似乎包含单价(正如人们通常发现的那样,除非在最好的餐厅),每个订单包含每个菜单项的编号这是订购的。假设:
@menu = {rice: 0.69, noodles: 0.89}
其中值是单位价格,orders
元素看起来像这样:
{rice: 3, noodles: 2}
其中值是订购数量。提供此订单给出的数量的成本为:
(3)(0.69) + (2)(0.89) = 3.95
您要将此费用与所有订单相加。
首先,让我们编写这样的方法,
def cost( *orders )
orders.inject(0) do |total_cost, order|
total_cost + order.keys.inject(0) do |cost, key|
cost + order[key] * @menu[key]
end
end
end
澄清其结构。 inject
(又名reduce
)正在迭代orders
并在变量total_cost
中累积一个值。您可以通过将参数传递给total_cost
来分配inject
初始值(正如您所做的那样)。如果您未给inject
一个参数初始值,则total_cost
设置为等于inject
后面的块中的第一个评估值。在这种情况下,如果将参数删除到inject
,则会得到相同的结果。
对于orders
的第一个值(块变量order
),将以下数字添加到累加器total_cost
:
order.keys.inject(0) do |cost, key|
cost + @menu[key] * order[key]
end
要获得此值,Ruby必须执行边计算。假设@menu
和order
具有我上面给出的值。
inject(0)
使用order.keys
作为累加器,对[:rice, :noodles]
(cost
)进行迭代。该块执行:rice
,然后执行noodles
:
cost + order[:rice] * @menu[:rice] => 0 + 3 * 0.69 # => 2.07 => cost
cost + order[:noodles] * @menu[:noodles] => 2.07 + 2 * 0.89 # => 3.95 => cost
这样就完成了边计算,因此3.95被添加到外部累加器total_cost
(之前等于零)。然后,orders
的下一个元素由外部inject
处理,依此类推。
答案 1 :(得分:0)
首先,了解ruby的Enumerable inject
是如何工作的。 “ruby inject and the Mandelbrot set”是关于它的介绍性文章。
基于这种理解,我们看到了这段代码:
order.keys.inject(0) {|cost, key| cost + @menu[key]*order[key] }
只是返回所有值@menu[key]*order[key]
的总和,key
遍历order.keys
,以计算每个订单的总费用。
最后,外部循环orders.inject(0) do |total_cost, order| ...
循环遍历orders
列表中每个订单的成本,以返回所有订单的总成本。
答案 2 :(得分:0)
帖子中cost
定义的关键显然是inject
方法。 inject
方法也可以调用为reduce
,这对于许多说英语的人来说是一个更明智的名称,因为它需要一个列表并将其减少为单个值。 (只是为了进一步混淆,在函数式编程的文献中,这个函数几乎总是被称为“折叠”)。
有很多例子;考虑找到整数列表的总和:
[1,2,3,4,5].inject(0) {|sum, num| return sum + num} #=> 15
那么这里发生了什么?块的第一个参数是运行结果 - 在这种情况下是部分和。它始于你传递给inject
的参数,在上面的例子中是0。
列表中每个项目调用一次,当前项目成为第二个参数。块返回的值成为块的下一次迭代的运行值(第一个参数)。
因此,如果我们将上述注入扩展为更明确的命令式代码,我们会得到类似的结果:
def block(sum, num)
return sum + num
end
result = 0
for i in [1,2,3,4,5]
result = block(result, i)
end
有了这些知识,让我们解决cost
:
def cost(*orders)
orders.inject(0) do |total_cost, order|
total_cost + order.keys.inject(0) {|cost, key| cost + @menu[key]*order[key] }
end
end
首先,它利用了这样一个事实:你可以在Ruby中放弃return
;块中最后一个表达式的值是块的返回值。
两个inject
调用看起来很像我上面的例子 - 它们只是简单的求和循环。
外部inject
在所有单个订单中构建总计,但由于这些订单是地图而不是数字,因此在将每个订单添加到一起之前,必须做更多的工作来获取每个订单的成本。 “更多工作”是内部inject
电话。
order.keys.inject(0) {|cost, key| cost + @menu[key]*order[key] }
使用上面的扩展,您可以看到它是如何工作的 - 它只是将按顺序(项目数量)乘以该项目(键的价格)的每个值相加的结果。
顺便提一下,您可以通过减少键/值对而不仅仅是值来避免在块内的顺序映射中查找键。您还可以利用以下事实:如果您未将初始值传递给inject
/ reduce
,则默认为零:
orders.inject { |grand_total, order|
grand_total + order.inject { |subtotal, line_item|
item, quantity = line_item
subtotal + quantity * @menu[item]
}
}