需要简单的注入方法说明


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

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

ruby  syntax 

3
参见Wikipedia:折叠(高阶函数):注入是“向左折叠”,尽管(不幸地)在Ruby使用中经常会产生副作用。
user2864740 2015年

Answers:


208

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

在过程结束时,注入返回累加器,在这种情况下,累加器是数组中所有值的总和,即10。

这是另一个简单的示例,该示例根据对象的字符串表示形式从对象数组创建哈希:

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

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


很好的解释,但是,在OP给出的示例中,返回了什么(例如您的示例中为hash)。它以结果+解释结尾,应该有一个返回值,是吗?
Projjol '16

1
@Projjol result + explanation既是对累加器的转换又是返回值。这是该块中的最后一行,使其隐式返回。
KA01

87

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

感谢您写出步骤。这很有帮助。尽管我对您是否要表示下图是在作为注入参数传递的方式下如何实现inject方法感到有些困惑。

2
下图是基于它是如何可能实现; 它不一定完全以这种方式实现。这就是为什么我说这是一组虚构的步骤。它展示了基本结构,但没有确切的实现。
布赖恩·坎贝尔

27

注入方法的语法如下:

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

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

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

给出10作为输出。

因此,在开始之前,让我们看一下每个变量中存储的值是什么:

结果= 0零来自inject(value),即0

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

哎呀!因此,让我们开始理解以上示例

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

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

步骤:3 [1, 2, 3, 4].inject(0) { |3, 3| 3 + 3 }

第4步 [1, 2, 3, 4].inject(0) { |6, 4| 6 + 4 }

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

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

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


19

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

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

15

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

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

是相同的

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

试试吧,我等。

如果没有传递参数以进行注入,则将前两个元素传递到第一次迭代中。在上面的示例中,结果是1,元素第一次是2,所以对该块的调用少了。


14

在注入()中放入的数字表示起始位置,可以是0或1000。在管道内,您有两个占位符| x,y |。x =您在.inject('x')中拥有的数字,秒代表对象的每次迭代。

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

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


6

注入应用块

result + element

到数组中的每个项目。对于下一项(“元素”),从块返回的值为“结果”。调用它(带有参数)的方式,“结果”从该参数的值开始。因此效果是将元素加在一起。


6

tldr; inject区别于map一个重要的方面: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


4
[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

3

[1, 2, 3, 4].inject(0) { |result, element| result + element } # => 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。


2

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

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

1

从这里开始,然后查看所有采用块的方法。 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 }

在元素检查器块中。


1

这是一个简单且相当容易理解的解释:

忘记“初始值”,因为它在开始时有些混乱。

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

您可以将以上内容理解为:我正在1,2,3,4之间注入“添加机”。意思是1 2 3 3 4并且♫是加法器,所以它与1 + 2 + 3 + 4相同,并且是10。

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

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

就像+在1,2,3,4之间注入a ,使它成为1 + 2 + 3 + 4 :+等于10。这是Ruby的一种+以符号形式指定的方式。

这是很容易理解和直观的。如果要逐步分析它的工作方式,则就像:取1和2,现在将它们相加,得到结果后,先存储(即3),然后存储下一个值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



0

这只是reduce或者fold,如果你熟悉其他语言。


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.