我正在计算项目在枚举中出现的次数。
irb(main):003:0> (1..3).reduce(0) {|sum, p| sum += 1 if p == 1}
=> nil
irb(main):004:0> (1..3).find_all{|p| p == 1}.length
=> 1
reduce方法似乎应该与find_all方法具有相同的行为。为什么它会返回nil
而不是1
?
irb(main):023:0> (1..3).reduce(0) {|sum, p| sum += 1 if p == 2}
NoMethodError: undefined method `+' for nil:NilClass
from (irb):23:in `block in irb_binding'
from (irb):23:in `each'
from (irb):23:in `reduce'
from (irb):23
from /usr/bin/irb:12:in `<main>'
第一次迭代出现问题。可以减少这种方式不使用吗?
答案 0 :(得分:6)
在reduce中,块中代码的值被分配给累加器。在你的情况下,你覆盖第一个赋值与后来的nils相加。
您可以通过以下方式解决此问题:
(1..3).reduce(0) {|sum, p| sum += 1 if p == 1; sum}
或
(1..3).reduce(0) {|sum, p| sum += p == 1 ? 1 : 0}
对于你的第二个例子,sum在第一次迭代时被赋值为nil,你试图在第二次迭代中将n加1。
请记住,减少/注入可能不是最好的计数工具 - 试试
(1..3).count(1)
答案 1 :(得分:5)
给予Enumerable#reduce
method的块的返回值(或最后一个值)总是存储为每次调用时累加器的新值,因此就地增加总和(sum+=1
)具有误导性。如果p==1
,则块返回预期值,否则返回nil
,因此累加器将被覆盖。
尝试修改您的块以始终返回累加器的期望值(总和),例如:
(1..3).reduce(0) { |sum,p| (p==1) ? sum+1 : sum }
reduce方法实现中的调用序列如下所示:
acc = 0
acc = yield(acc, 1) # (sum=0, p=1) => sum+1 => 1
acc = yield(acc, 2) # (sum=1, p=2) => sum => 1
acc = yield(acc, 3) # (sum=1, p=3) => sum => 1
acc # => 1