1

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

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

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

4

2 回答 2

7

在reduce中,块中代码的值被分配给累加器。在您的情况下,您将第一个分配覆盖为与后来的 nil 相加。

您可以通过以下方式解决此问题:

(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,而您试图在第二次迭代中将 1 添加到 nil 中。

请记住,reduce/inject 可能不是最好的计数工具 - 试试

(1..3).count(1)
于 2012-09-19T04:30:03.403 回答
5

Enumerable#reduce方法的块的返回值(或最后一个值)总是在每次调用时存储为累加器的新值,因此就地递增总和 ( 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
于 2012-09-19T04:57:13.477 回答