【问题标题】:How to sum properties of the objects within an array in Ruby如何在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 组成的新数组并将其相加,但如果可能的话,我正在寻找一种更简洁的方法!

【问题讨论】:

    标签: ruby arrays object sum inject


    【解决方案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
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-10-14
          • 1970-01-01
          相关资源
          最近更新 更多