如何在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中对数组中对象的属性求和的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Postgresql 中对 JSONB 数组中的值求和?

如何在 C/C++ 中对二维数组求和

如何使用 AngularJs 对对象数组中的属性值求和

如何根据用户输入在 C# 中对对象数组进行排序?

如何在Ruby中的Array类中对数组的每个元素进行平方?

如何从 Ruby 中的数组返回属性值?