考虑以下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
是为累加器存储的最后一个值,因此应该返回它。
答案 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