您帖子中定义的关键cost
显然是inject
方法。该inject
方法也可以调用 as 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
首先,它利用了return
在 Ruby 中可以省略的事实;块中最后一个表达式的值是块的返回值。
这两个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]
}
}