如何在Ruby中对数组中对象的属性求和
Posted
技术标签:
【中文标题】如何在Ruby中对数组中对象的属性求和【英文标题】:How to sum properties of the objects within an array in Ruby 【发布时间】:2012-07-01 15:25:46 【问题描述】:我知道为了在 Ruby 中对数组元素求和,可以使用注入方法,即
array = [1,2,3,4,5];
puts array.inject(0, &:+)
但是我如何对对象数组中对象的属性求和?
例如,有一个对象数组,每个对象都有一个属性“cash”。所以我想把他们的现金余额加起来。比如……
array.cash.inject(0, &:+) # (but this doesn't work)
我意识到我可能会创建一个仅由属性 cash 组成的新数组并将其相加,但如果可能的话,我正在寻找一种更简洁的方法!
【问题讨论】:
【参考方案1】:array.map(&:cash).inject(0, &:+)
或
array.inject(0)|sum,e| sum + e.cash
【讨论】:
这会重复array
两次,如果有很多元素,这可能是不可取的。为什么不对inject
使用适当的块?同样reduce/inject
直接接受符号参数,不需要Symbol#to_proc
:-)
请注意,您不需要发送块,inject
知道如何处理符号:inject(0, :+)
Yuri,我为你的答案 +1,但第二个 sn-p 看起来不太好,功能更好:array.inject(0) |sum, e| sum + e.cash
我认为这可能是我的错)【参考方案2】:
在 Ruby On Rails 中你也可以尝试:
array.sum(&:cash)
它是注入业务的捷径,对我来说似乎更具可读性。http://api.rubyonrails.org/classes/Enumerable.html
【讨论】:
如果您使用的是 Rails,这就是要走的路。 请注意,如果您的数组是对 ActiveRecord 对象进行某种过滤的结果,例如@orders = Order.all; @orders.select |o| o.status == 'paid' .sum(&:cost)
,那么你也可以通过查询得到同样的结果:@orders.where(status: :paid).sum(:cost)
。
如果记录没有存储在数据库中,总和将为0,注入将起作用。
更多关于@Dennis 评论:如果你使用的是 Rails 4.1+,你不能 array.sum(&:cash)
在一个 activerecord 关系上,因为它想要创建一个 ActiveRecord sum像这样:array.sum(:cash)
非常不同(SQL 与 Ruby)。您必须将其转换为数组才能使其再次工作:array.to_a.sum(&:cash)
。好恶心!
@AugustinRiedinger 如果可能的话,最好是做 sql sum vs ruby sum,不是吗?【参考方案3】:
#reduce
接受一个块(&:+
是创建一个执行+
的过程/块的快捷方式)。这是做你想做的事情的一种方式:
array.reduce(0) |sum, obj| sum + obj.cash
【讨论】:
#reduce
是 1.9+ 中 #inject
的别名,顺便说一句。
+1 表示不迭代 array
两次。顺便说一句,别名也在 1.8.7 中。
正如迈克尔所说,这比 map+reduce 更节省空间,但以模块化为代价(在这种情况下很小,无需多说)。在 Ruby 2.0 中,由于懒惰,我们可以两者兼得:array.lazy.map(&:cash).reduce(0, :+)
。
不知道为什么会有这样的别名。它们的长度相同。
@Nerian:在 Smalltalk 中,这称为 inject:into:
,而其他几种语言称为折叠 reduce
(例如 Clojure、Common Lisp、Perl、Python)。别名用于容纳具有不同背景的人。 map
/collect
也一样。【参考方案4】:
最简洁的方式:
array.map(&:cash).sum
如果地图的结果数组包含 nil 项:
array.map(&:cash).compact.sum
【讨论】:
【参考方案5】:如果求和的起始值为 0,则单独求和与注入相同:
array.map(&:cash).sum
我更喜欢块版本:
array.sum |a| a.cash
因为 Proc from 符号通常太有限(没有参数等)。
(需要 Active_Support)
【讨论】:
【参考方案6】:这里有一些有趣的基准
array = Array.new(1000) OpenStruct.new(property: rand(1000))
Benchmark.ips do |x|
x.report('map.sum') array.map(&:property).sum
x.report('inject(0)') array.inject(0) |sum, x| sum + x.property
x.compare!
end
结果
Calculating -------------------------------------
map.sum 249.000 i/100ms
inject(0) 268.000 i/100ms
-------------------------------------------------
map.sum 2.947k (± 5.1%) i/s - 14.691k
inject(0) 3.089k (± 5.4%) i/s - 15.544k
Comparison:
inject(0): 3088.9 i/s
map.sum: 2947.5 i/s - 1.05x slower
如您所见,注入速度更快
【讨论】:
【参考方案7】:inject中不需要使用initial,加号操作可以更短
array.map(&:cash).inject(:+)
【讨论】:
符号参数你是对的,但是如果array
可以为空,你需要参数:[].inject(:+) #=> nil
,[].inject(0, :+) #=> 0
,除非你想单独处理nil
。
以上是关于如何在Ruby中对数组中对象的属性求和的主要内容,如果未能解决你的问题,请参考以下文章