1

我正在努力理解 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简单地解释该方法中的每个步骤吗?

4

3 回答 3

4

考虑到 atotal 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 必须进行边计算。假设@menuorder具有我上面给出的值。

inject(0)迭代order.keys(即[:rice, :noodles]),cost用作其累加器。该块被执行 for:rice然后 for 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,依此类推。

于 2013-10-31T23:56:54.660 回答
0

首先,了解 ruby​​ 的 Enumerable是如何inject工作的。“ ruby​​ injection 和 Mandelbrot set ” 是一篇关于它的介绍性文章。

基于这种理解,我们看到这段代码:

order.keys.inject(0) {|cost, key| cost + @menu[key]*order[key] }

只是在迭代时返回所有值@menu[key]*order[key]的总和,以给出每个订单的总成本。keyorder.keys

最后,外层循环orders.inject(0) do |total_cost, order| ...遍历列表中每个订单的成本orders,返回所有订单的总成本。

于 2013-10-31T22:27:34.087 回答
0

您帖子中定义的关键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]
  }
}
于 2013-11-01T04:01:46.243 回答