如果块永远不会更改累加器,为什么不减少返回初始值?

时间:2017-09-24 16:00:54

标签: ruby

考虑以下irb循环:

irb(main):015:0> [nil, nil].reduce(0) { |accum, x| accum + 1 unless x.nil? }
=> nil

为什么这会返回nil而不是0

根据Ruby Enumerable文档:

  

如果指定一个块,那么对于枚举中的每个元素,块将传递一个累加器值(memo)和元素。如果指定了符号,则集合中的每个元素都将传递给命名的备忘录方法。在任何一种情况下,结果都将成为备忘录的新值。在迭代结束时,memo的最终值是方法的返回值。

我的期望是在数组开始折叠之前,累加器应设置为0,因为它是作为初始值给出的。然后,块的转义子句将触发此数组中的所有元素,因此累加器将永远不会更改。最后,由于0是为累加器存储的最后一个值,因此应该返回它。

3 个答案:

答案 0 :(得分:1)

无论块返回什么,都将成为下一个累加器值。

然后你返回nil

'whatever' unless true #=> nil

你可以这样做:

arr.reduce(0) { |a, e| e.nil? ? a : a + 1 }

或者这个:

arr.compact.reduce(0) { |a, e| a + 1 }

或者这个:

arr.compact.size

答案 1 :(得分:1)

使用您传递的累加器减少启动,然后将其设置为块返回的任何内容。

看看它内部的作用可能会有所帮助(这不是实际的源代码,只是简单的再现):

class Array
  def my_reduce(memo, &blk)
    each { |i| memo = blk.call(memo, i) }
    memo
  end
end

以下是一些展示其用法的示例:

# with 0 as starting memo
[nil, nil].reduce(0) { |memo, i| i ? memo + 1 : memo } # => 0
[nil, nil].reduce(0) { |memo, i | memo += 1 if i; memo; } # => 0
[nil, nil].reduce(0) { |memo, i| memo + (i ? 1 : 0) } # => 0

# with [] as starting memo
[1,2].reduce([]) { |memo, i| memo.push(i + 1); memo } # => [2,3]
[1,2].reduce([]) { |memo, i| memo.concat([i + 1]) } # => [2,3]
[1,2].reduce([]) { |memo, i| memo + [i + 1] } # => [2,3]
[1,2].reduce([]) { |memo, i| [*memo, i + 1] } # => [2,3]

您可以看到其中只有一些需要memo作为最后一行返回。那些没有利用返回修改过的对象的方法,而不是依赖于可变性(memo.push)或局部变量赋值(memo += 1)

each_with_object与reduce基本相同,只是它会自动从每个块返回累加器,并反转块args的顺序(|i, memo|而不是|memo, i)。当备忘录是一个可变对象时,它可以很好地减少语法糖。在以下示例中不再需要从块中返回备忘录:

[1,2].each_with_object([]) { |i, memo| memo.push(i + 1) } # => [2,3]

然而,它不能使用您的原始示例,因为备忘录(数字)是不可变的:

# returns 0 but should return 1
[true, nil].each_with_object(0) { |i, memo| memo += 1 if i }

memo += 1这里只是局部变量赋值。请记住,您永远不能为ruby中的对象更改self的值,甚至不能更改为可变对象的值。如果Ruby有增量运算符(i++)那么它可能是一个不同的故事(见no increment operator in ruby

答案 2 :(得分:0)

您的期望是正确的,但缺失的部分是考虑执行后块返回的内容。

在Ruby中,返回最后执行的内容,此代码:accum + 1 unless x.nil?返回nil

仅供科学使用:

irb(main):051:0> puts 'ZOMG' unless nil.nil?
=> nil

由于阻止了nil,因此累加器的初始0会被nil覆盖。

如果您修改代码以返回累加器,您将按预期获得0

irb(main):052:0> [nil, nil].reduce(0) do |accum, x|
irb(main):053:1*     accum + 1 unless x.nil?
irb(main):054:1>     accum
irb(main):055:1> end
=> 0