2016-01-21 17 views
0

我on Rails項目工作如何遞增散列/對象的值沒有確定前他們

我的產品以其獨特的代碼的哈希作爲密鑰

products = [ 
    {"dt": "2016-01-01", 
    "quantity": 122, 
    "amount": 123000 
    }, 
    {"dt": "2016-01-02", 
    "quantity": 97, 
    "amount": 97800 
    } 
    {"dt": "2016-01-03", 
    "quantity": 142, 
    "amount": 163000 
    } 
] 

我的目標是創建這3天報告的數量之和。我遍歷該對象。我想爲每個迭代中增加的名爲「total_quantity」的數組中的每個對象添加新屬性。

products.each do |row| 
    rowqty += row['quantity'] 
    rowamount += row['amount'] 
    row['total_quantity'] = rowqty 
    row['total_amount'] = rowamount 
end 

你可以看到,我定義了兩個額外的變量「rowqty」和「rowamount」用它們來創建每個對象的新屬性之前舉行更新的數據。

,但我得到這個錯誤

undefined method `+' for nil:NilClass 

然而,迭代之前,如果我預先定義的變量,它似乎工作。像這樣

rowqty = 0 
rowamount = 0 

products.each do |row| 
    rowqty += row['quantity'] 
    rowamount += row['amount'] 
    row['total_quantity'] = rowqty 
    row['total_amount'] = rowamount 
end 

我知道它需要變量存在才能對它做些什麼。但是我不得不承認,當數據變得複雜的時候,我會遇到這樣的問題。

因此,無論如何應用數學運算,如增加,減少或任何與不在範圍內退出的變量?

在PHP中,它似乎創建了新的變量。這包括數組和對象。但我不能做在Rails的

+1

只需在您的標籤選擇上挑選一個即使您可能將其作爲Rails項目的一部分,這完全是一個Ruby問題。 – dwenzel

回答

2

在PHP中,未定義的變量將默認爲0和實例化時,你第一次嘗試添加一些內容。

$unknown_var += 1; // This works! 

在Ruby中,如果你試圖做同樣的事情,你會得到一個錯誤,所以你必須定義變量,然後才能增加它:

known_var = 0 
known_var += 1 # This works, but not if you don't define your variable first 

在回答你的問題,你會只需要在使用它們之前定義變量。重要的是,你必須在你的循環內生成的範圍外定義你的變量,否則變量會在你的循環運行時重置,你將無法按照你的方式在你的數組中執行總和計算。

你的問題中的最後一個例子是正確的方法來做到這一點,而不使用像inject更復雜的東西。

按塞瑪的答案,使用注射,你可以實現相同的加法,但不要忘了還要修改你的初始數據集:

products.inject([0, 0]) do |data, product| 
    data[0] += product['quantity'] 
    data[1] += product['amount'] 
    product['total_quantity'] = data[0] 
    product['total_amount'] = data[1] 
    data 
end 

然而,當你調用inject這仍然會定義一個局部變量並且會在數組的每次迭代中增加它內部的值。

順便說一下,測試這兩個選項的速度顯示你原來的解決方案,以最快的速度:

Calculating ------------------------------------- 
    Using Array#each 36.667k i/100ms 
    Using Array#inject 31.819k i/100ms 
------------------------------------------------- 
    Using Array#each 427.696k (± 7.8%) i/s -  2.127M 
    Using Array#inject 362.753k (± 4.2%) i/s -  1.814M 

Comparison: 
    Using Array#each: 427695.7 i/s 
    Using Array#inject: 362753.2 i/s - 1.18x slower 

代碼用於生成這個基準:https://gist.github.com/pacso/f7e997593bd15bd121d1

+0

很好的答案。它真棒,你真的花了時間來分析每個vs注入。 – max

+0

謝謝@max--好奇心在那個上得到了更好的體現;) – Jon

1

您可以使用方法reduce數組:

# returns array of 2 elements with summary values 
def calculate_sums(products) 
    products.reduce([0, 0]) do |sum, product| 
    sum[0] += product['quantity'] 
    sum[1] += product['amount'] 
    sum 
    end 
end 

# somewhere in source code 
total_quantity, total_amount = calculate_sums(products) 

不知道,這個代碼比局部變量:)的情況下更好地

一點解釋: reduce接受初始值[0, 0]並遍歷數組,並在每次迭代中注入sum變量。

PS。如果您products變量來自ActiveRecord的,整齊的方式calculate sums會像

products.sum('quantity') 
products.sum('amount') 
1

從你的描述「當數據變得更更復雜「,它看起來像代碼開發過程中的變量清單可能會頻繁更改。在這種情況下,你不應該保留單個變量,而只需要一個哈希來保存所有的變量。

如果你這樣做,很容易實現你所要求的。 h = Hash.new(0)將初始化一個散列,其所有潛在的密鑰將自動初始化爲0,當調用類似+=的語法糖方法時。

h = Hash.new(0) 
products.each do |row| 
    h[:rowqty] += row['quantity'] 
    h[:rowamount] += row['amount'] 
    row['total_quantity'] = rowqty 
    row['total_amount'] = rowamount 
end