【问题标题】:Ruby coding exercise solution (rubymonk)Ruby 编码练习解决方案(rubymonk)
【发布时间】:2013-11-12 02:12:22
【问题描述】:

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

【问题讨论】:

  • 查找 spalt 运算符并查看一些使用中的注入函数示例。
  • @hirolau, s/spalt/splat/.
  • 如果您对任何答案感到满意,您应该选择对您最有帮助的答案。
  • 感谢您的提醒,我想知道我应该如何表明这一点。
  • 您需要了解的关于 SO 程序和礼仪的大部分内容是here

标签: ruby class methods hash inject


【解决方案1】:

考虑到正在计算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中累积一个值。您可以通过将参数传递给inject 来分配total_cost 一个初始值(正如您所做的那样)。如果你不给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处理,以此类推。

【讨论】:

    【解决方案2】:

    首先,了解 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列表中每个订单的成本,返回所有订单的总成本。

    【讨论】:

    • 马特,感谢您提供的信息。这篇文章读起来很好,虽然我在阅读后并没有马上把它全部整理好,但我现在能够掌握并理解在这种情况下注入的情况。我认为作为初学者的部分问题是在这些通用练习中无意中过多地关注变量名,而不是方法实际如何工作,从而导致与正在发生的事情混淆......如果这有任何意义!
    【解决方案3】:

    您帖子中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]
      }
    }
    

    【讨论】:

      猜你喜欢
      • 2014-07-01
      • 2012-03-30
      • 2017-12-20
      • 2017-07-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-12-11
      相关资源
      最近更新 更多