2016-01-21 53 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