【问题标题】:Need a simple explanation of the inject method需要简单解释一下inject方法
【发布时间】:2009-04-02 16:28:51
【问题描述】:
[1, 2, 3, 4].inject(0) { |result, element| result + element } # => 10

我正在查看此代码,但我的大脑并没有记录数字 10 是如何成为结果的。有人介意解释一下这里发生了什么吗?

【问题讨论】:

  • 参见Wikipedia: Fold (higher-order function):inject 是“向左折叠”,尽管(不幸的是)在使用 Ruby 时经常会产生副作用。
  • 如果您想消化比维基百科文章from this answer below更少的数学可视化。
  • 我更喜欢#inject 的别名#reduce,因为这些操作是如何工作的。当您以一般方式使用#map 时,您将获得与“放入”#map 相同数量的元素,您将获得一对一的映射。使用#reduce,您会得到更少的 - 通常是 1 - 元素,因此命名为reduce。当然,这是 Ruby,您可以调整 #map 和 #reduce 以使其行为不同。

标签: ruby syntax


【解决方案1】:

您可以将第一个块参数视为累加器:块的每次运行的结果都存储在累加器中,然后传递给块的下一次执行。在上面显示的代码的情况下,您将累加器结果默认为 0。该块的每次运行都会将给定的数字添加到当前总数中,然后将结果存储回累加器中。下一个块调用具有这个新值,添加到它,再次存储它,然后重复。

在进程结束时,inject 返回累加器,在本例中是数组中所有值的总和,即 10。

这是另一个从对象数组创建哈希的简单示例,以字符串表示为键:

[1,"a",Object.new,:hi].inject({}) do |hash, item|
  hash[item.to_s] = item
  hash
end

在这种情况下,我们将累加器默认为空哈希,然后在每次块执行时填充它。请注意,我们必须将哈希值作为块的最后一行返回,因为块的结果将存储回累加器中。

【讨论】:

  • 很好的解释,但是,在 OP 给出的示例中,返回的是什么(就像你的示例中的 hash 一样)。它以结果+解释结束,应该有一个返回值,是吗?
  • @Projjol result + explanation 既是对累加器的转换,也是返回值。这是块中的最后一行,使其成为隐式返回。
【解决方案2】:

inject 以一个值开头(在您的示例中为 0)和一个块,它为列表的每个元素运行该块一次。

  1. 在第一次迭代中,它传入您提供的值作为起始值和列表的第一个元素,并保存您的块返回的值(在本例中为 result + element)。
  2. 然后它再次运行该块,将第一次迭代的结果作为第一个参数传入,将列表中的第二个元素作为第二个参数传入,再次保存结果。
  3. 它继续这种方式,直到它消耗完列表的所有元素。

解释这一点的最简单方法可能是展示每个步骤的工作原理,例如您的示例;这是一组假想的步骤,展示了如何评估此结果:

[1, 2, 3, 4].inject(0) { |result, element| result + element }
[2, 3, 4].inject(0 + 1) { |result, element| result + element }
[3, 4].inject((0 + 1) + 2) { |result, element| result + element }
[4].inject(((0 + 1) + 2) + 3) { |result, element| result + element }
[].inject((((0 + 1) + 2) + 3) + 4) { |result, element| result + element }
(((0 + 1) + 2) + 3) + 4
10

【讨论】:

  • 感谢您写出这些步骤。这有很大帮助。尽管我有点困惑您是否是说下图是根据作为参数传递给注入的内容在下面实现注入方法的方式。
  • 下图是基于它可以如何实现的;它不一定以这种方式实现。这就是为什么我说这是一组虚构的步骤;它演示了基本结构,但不是确切的实现。
  • 这很有帮助,我试过visualize this in my answer below
【解决方案3】:

inject 方法的语法如下:

inject (value_initial) { |result_memo, object| block }

让我们解决上面的例子,即

[1, 2, 3, 4].inject(0) { |result, element| result + element }

10 作为输出。

所以,在开始之前,让我们看看每个变量中存储的值是什么:

result = 0 零来自 inject(value),即 0

element = 1 它是数组的第一个元素。

好的!!!那么,让我们开始理解上面的例子

步骤:1 [<b><i>1</i></b>, 2, 3, 4].inject(<b>0</b>) { |<b>0</b>, <i><b>1</b></i>| <b>0</b> + <i><b>1</b></i> }

步骤:2 [1, <b><i>2</i></b>, 3, 4].inject(0) { |<b>1</b>, <b><i>2</i></b>| <b>1</b> + <b><i>2</i></b> }

步骤:3 [1, 2, <b><i>3</i></b>, 4].inject(0) { |<b>3</b>, <b><i>3</i></b>| <b>3</b> + <b><i>3</i></b> }

步骤:4 [1, 2, 3, <b><i>4</i></b>].inject(0) { |<b>6</b>, <b><i>4</i></b>| <b>6</b> + <b><i>4</i></b> }

步骤:5 [1, 2, 3, 4].inject(0) { |<b>10</b>, <b><i>Now no elements left in the array, so it'll return 10 from this step</i></b>| }

这里的 Bold-Italic 值是从数组中获取的元素,而简单的 Bold 值是结果值。

希望您了解#ruby#inject 方法的工作原理。

【讨论】:

    【解决方案4】:

    代码遍历数组中的四个元素,并将先前的结果添加到当前元素:

    • 1 + 2 = 3
    • 3 + 3 = 6
    • 6 + 4 = 10

    【讨论】:

      【解决方案5】:

      他们说了什么,但也请注意,您并不总是需要提供“起始值”:

      [1, 2, 3, 4].inject(0) { |result, element| result + element } # => 10
      

      相同
      [1, 2, 3, 4].inject { |result, element| result + element } # => 10
      

      试试吧,我等着。

      当没有参数传递给注入时,前两个元素被传递到第一次迭代中。在上面的示例中,第一次结果为 1,元素为 2,因此对块的调用减少了一次。

      【讨论】:

        【解决方案6】:

        inject () 中的数字表示起始位置,可以是 0 或 1000。 在管道内有两个占位符|x, y|。 x = 你在 .inject('x') 中的任何数字,第二个代表你的对象的每次迭代。

        [1, 2, 3, 4].inject(5) { |result, element| result + element } # =&gt; 15

        1 + 5 = 6 2 + 6 = 8 3 + 8 = 11 11 + 4 = 15

        【讨论】:

          【解决方案7】:

          Inject 应用块

          result + element
          

          到数组中的每个项目。对于下一项(“元素”),从块返回的值是“结果”。您调用它的方式(带有参数),“结果”从该参数的值开始。所以效果就是把元素加起来。

          【讨论】:

            【解决方案8】:

            tldr; injectmap 在一个重要方面不同:inject 返回块的最后一次执行的值,而 map 返回迭代的数组。

            不仅如此每个块执行的值通过第一个参数(在本例中为result)传递给下一个执行,您可以初始化该值((0) 部分)。

            你上面的例子可以用map这样写:

            result = 0 # initialize result
            [1, 2, 3, 4].map { |element| result += element }
            # result => 10
            

            同样的效果,但inject在这里更简洁。

            您经常会发现赋值发生在 map 块中,而评估发生在 inject 块中。

            您选择哪种方法取决于您希望result 的范围。什么时候使用它会是这样的:

            result = [1, 2, 3, 4].inject(0) { |x, element| x + element }
            

            您可能会像所有人一样,“看我,我只是将所有内容合并到一行中”,但您还临时为 x 分配了内存作为临时变量,因为您已经有 result 到一起工作。

            【讨论】:

              【解决方案9】:
              [1, 2, 3, 4].inject(0) { |result, element| result + element } # => 10
              

              等价于:

              def my_function(r, e)
                r+e
              end
              
              a = [1, 2, 3, 4]
              result = 0
              
              a.each do |value|
                result = my_function(result, value)
              end
              

              【讨论】:

                【解决方案10】:

                [1, 2, 3, 4].inject(0) { |result, element| result + element } # =&gt; 10

                用简单的英语,你正在遍历(迭代)这个数组([1,2,3,4])。您将遍历此数组 4 次,因为有 4 个元素(1、2、3 和 4)。注入方法有 1 个参数(数字 0),您将该参数添加到第一个元素(0 + 1。这等于 1)。 1 保存在“结果”中。然后将该结果(即 1)添加到下一个元素(1 + 2。这是 3)。 这个现在将被保存为结果。继续:3 + 3 等于 6。最后,6 + 4 等于 10。

                【讨论】:

                  【解决方案11】:

                  此代码不允许不传递起始值,但可能有助于解释发生了什么。

                  def incomplete_inject(enumerable, result)
                    enumerable.each do |item|
                      result = yield(result, item)
                    end
                    result
                  end
                  
                  incomplete_inject([1,2,3,4], 0) {|result, item| result + item} # => 10
                  

                  【讨论】:

                    【解决方案12】:

                    这是一个简单易懂的解释:

                    忘记“初始值”,因为它一开始有点混乱。

                    > [1,2,3,4].inject{|a,b| a+b}
                    => 10
                    

                    你可以把上面理解为:我在1、2、3、4之间注入了一个“加法机”。意思是1 ♫ 2 ♫ 3 ♫ 4,♫是加法机,所以和1 + 2 + 3 + 4一样,都是10。

                    您实际上可以在它们之间注入+

                    > [1,2,3,4].inject(:+)
                    => 10
                    

                    就像,在 1,2,3,4 之间注入一个+,使其成为 1 + 2 + 3 + 4,它是 10。:+ 是 Ruby 指定 + 的方式符号的形式。

                    这很容易理解和直观。而如果你要一步一步分析它是如何工作的,那就是:取1和2,现在相加,当你有结果时,首先存储它(也就是3),现在,next是存储的值3和数组元素3经过a+b过程,也就是6,现在存储这个值,现在6和4经过a+b过程,就是10。你本质上是在做

                    ((1 + 2) + 3) + 4
                    

                    并且是 10。“初始值”0 只是一个“基数”。在许多情况下,您不需要它。想象一下,如果你需要 1 * 2 * 3 * 4 并且它是

                    [1,2,3,4].inject(:*)
                    => 24
                    

                    然后就完成了。您不需要1 的“初始值”来将整个事物与1 相乘。这一次,它在做

                    (((1 * 2) * 3) * 4)
                    

                    你得到与

                    相同的结果
                    1 * 2 * 3 * 4
                    

                    【讨论】:

                      【解决方案13】:

                      从这里开始,然后查看所有采用块的方法。 http://ruby-doc.org/core-2.3.3/Enumerable.html#method-i-inject

                      是让你感到困惑的块还是为什么你在方法中有一个值? 不过是个好问题。那里的操作符方法是什么?

                      result.+
                      

                      它的开头是什么?

                      #inject(0)
                      

                      我们可以这样做吗?

                      [1, 2, 3, 4].inject(0) { |result, element| result.+ element }
                      

                      这行得通吗?

                      [1, 2, 3, 4].inject() { |result = 0, element| result.+ element }
                      

                      你看我是在建立一个想法,即它只是简单地对数组的所有元素求和,并在你在文档中看到的备忘录中产生一个数字。

                      你总是可以这样做

                       [1, 2, 3, 4].each { |element| p element }
                      

                      查看数组的可枚举项被迭代。这就是基本思想。

                      只是注入或减少给你一个备忘录或一个被发送出去的累加器。

                      我们可以尝试得到结果

                      [1, 2, 3, 4].each { |result = 0, element| result + element }
                      

                      但是什么都没有回来,所以这只是和以前一样

                      [1, 2, 3, 4].each { |result = 0, element| p result + element }
                      

                      在元素检查器块中。

                      【讨论】:

                        【解决方案14】:

                        有一天,我也对 Ruby 注入/减少方法中的默认值感到头疼,所以我尝试visualize my issue

                        【讨论】:

                          【解决方案15】:

                          还有另一种形式的 .inject() 方法非常有用 [4,5].inject(&:+) 这将把该区域的所有元素加起来

                          【讨论】:

                            【解决方案16】:

                            使用任何类型的集合时出现的一种常见情况是对所有元素执行单一类型的操作并收集结果。

                            例如,sum(array) 函数可能希望将作为数组传递的所有元素相加并返回结果。

                            Ruby 中以 reduce 的名义提供了相同功能的通用抽象(inject 是一个别名)。也就是说,这些方法遍历集合并使用运算符累积对基值中元素的操作值,并最终返回该基值。

                            让我们举个例子更好地理解。

                            >>> (5..10).inject(1) {|product, n| product * n }
                            => 151200
                            

                            在上面的示例中,我们有以下元素:基值 1、可枚举 (5..10) 和一个带有表达式的块,该块指示如何将计算值添加到基值(即,将数组元素乘以产品,其中产品使用基值初始化)

                            所以执行如下:

                            # loop 1
                            n = 1
                            product = 1
                            return value = 1*1
                            
                            # loop 2
                            n = 2
                            product = 1
                            return value = 1*2
                            
                            n = 3
                            product = 2
                            return value = 2*3
                            

                            .. 如您所见,当表达式循环遍历容器的元素时,基值会不断更新,从而返回基值的最终值作为结果。

                            【讨论】:

                              【解决方案17】:

                              和这个是一样的:

                              [1,2,3,4].inject(:+)
                              => 10
                              

                              【讨论】:

                              • 虽然这是事实,但不能回答问题。
                              【解决方案18】:

                              如果您熟悉其他语言,只需 reducefold

                              【讨论】:

                                猜你喜欢
                                • 2017-01-02
                                • 1970-01-01
                                • 1970-01-01
                                • 2014-09-14
                                • 2012-10-29
                                • 1970-01-01
                                • 2019-05-21
                                • 2019-02-21
                                • 2013-10-30
                                相关资源
                                最近更新 更多