Ruby编码运动解决方案(rubymonk)

时间:2013-10-31 21:52:34

标签: ruby class methods hash inject

我正在努力理解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方法中的每一步吗?

3 个答案:

答案 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必须执行边计算。假设@menuorder具有我上面给出的值。

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]
  }
}