问题
I'm counting the number of times an item appears in an Enumeration.
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
The reduce method seems like it should have the same behaviour as the find_all method. Why does it return nil instead of 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>'
Something is going wrong in the first iteration. Can reduce just not be used this way?
回答1:
In reduce, the value of the code in the block is assigned to the accumulator. In your case you override the first assignment to sum with later nils.
You can fix this by:
(1..3).reduce(0) {|sum, p| sum += 1 if p == 1; sum}
or
(1..3).reduce(0) {|sum, p| sum += p == 1 ? 1 : 0}
For your second example, sum is assigned nil on the first iteration, and you are trying to add 1 to nil on the second.
Please keep in mind that reduce/inject is probably not the best instrument for counting - try
(1..3).count(1)
回答2:
The return value (or last value) from the block given to the Enumerable#reduce method is always stored as the new value of the accumulator at each call, so incrementing the sum in-place (sum+=1) is misleading. Your block returns the expected value if p==1 but nil otherwise, so the accumulator gets overwritten.
Try modifying your block to always return the expected value of the accumulator (sum), e.g.:
(1..3).reduce(0) { |sum,p| (p==1) ? sum+1 : sum }
The call sequence in the reduce method implementation will look something like this:
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
来源:https://stackoverflow.com/questions/12488579/reduce-returns-nil-on-summation