2012-09-19 59 views
0

我正在計算項目在枚舉中出現的次數。總計減少回報無

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

第一次迭代中出現問題。只能減少這種方式嗎?

+3

當'p'不是'1'時,如果p == 1,sum + = 1的結果是'nil'。該塊的結果被賦予'sum',所以nils覆蓋了你的'sum'值。 – oldergod

回答

5

在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} 

對於你的第二個例子,和被分配在第一次迭代爲零,而你正在嘗試1添加到零上第二。

請記住,減少/注射可能不是用於計數的最佳工具 - 嘗試

(1..3).count(1) 
+0

檢查@maerics答案,看看更好的方法來寫這個塊 – tokland

+1

這取決於你想回答 - OP問題(爲什麼?)或如何更好地實現這一點(作爲紅寶石的一切,它可能是非常主觀的。認爲例如'sum + p == 1?1:0'更好地捕獲計數語義)。顯然,更好的實現並不是完全使用注入,你可以在'(1..3).count(1)' – UncleGene

+0

理想情況下解釋:「你通過這樣做來解決它,但是最好的方法來做到這一點正在這樣做「 – tokland

5

返回值(或最後一個值)給予Enumerable#reduce method塊是總是存儲每次調用時累加器的新值,因此增加原地總和(sum+=1)具有誤導性。如果p==1但是nil否則您的塊會返回預期值,因此累加器會被覆蓋。

嘗試修改您的塊總是返回累加器(總和)的預期值,例如:

(1..3).reduce(0) { |sum,p| (p==1) ? sum+1 : sum } 

在減少方法實現調用序列將是這個樣子:

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