总和时减少收益为零

时间:2012-09-19 04:26:07

标签: ruby reduce

我正在计算项目在枚举中出现的次数。

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>'

第一次迭代出现问题。可以减少这种方式不使用吗?

2 个答案:

答案 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