Ruby中的累积数组求和

36
什么是最流畅、最类似Ruby的方法来计算数组的累加和?
例如:
[1,2,3,4].cumulative_sum

应该返回

[1,3,6,10]

1
回到这个问题,仅仅5.5年后的我自己! - Peter
8个回答

49
class Array
  def cumulative_sum
    sum = 0
    self.map{|x| sum += x}
  end
end

14

以下是一种方法

a = [1, 2, 3, 4]
a.inject([]) { |x, y| x + [(x.last || 0) + y] }

如果答案可以由多个语句组成,那么这样做会更清晰:

outp = a.inject([0]) { |x, y| x + [x.last + y] }
outp.shift # To remove the first 0

7
 irb> a = (1..10).to_a
 #=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
 irb> a.inject([0]) { |(p,*ps),v| [v+p,p,*ps] }.reverse[1..-1]
 #=> [1, 3, 6, 10, 15, 21, 28, 36, 45, 55]

我们也可以借鉴Haskell的思路,制作一个Ruby版本的scanr
irb> class Array
   >   def scanr(init)
   >     self.inject([init]) { |ps,v| ps.unshift(yield(ps.first,v)) }.reverse
   >   end
   > end
#=> nil
irb> a.scanr(0) { |p,v| p + v }
=> [0, 1, 3, 6, 10, 15, 21, 28, 36, 45, 55]
irb> a.scanr(0) { |p,v| p + v }[1..-1]
=> [1, 3, 6, 10, 15, 21, 28, 36, 45, 55]
irb> a.scanr(1) { |p,v| p * v }
=> [1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880, 3628800]

1
+1 对于提到 scanr 的参考,那就是一个累积注入。请注意,您可以将 scanr 添加到 Enumerable 模块中,而不仅仅是 Array 类。 - tokland

2
此外,您可以阅读有关 scanl 的内容 - 这是您需要的功能,但据我所知,在 Ruby 中还没有实现。以下是它的示例和示例源代码:http://billsix.blogspot.com/2008/11/functional-collection-patterns-in-ruby.html 更新: 上面的链接已失效,因此我会说一下我的 Ruby 实现 Wolfram Mathematica 的 FoldList [],作为 gem “mll”的一部分。在这里,可用于 OP 目的,同时也是 Enumerable:
def fold_list array
  start = 0
  Enumerator.new do |e|
    array.each do |i|
      e << start += i
    end
  end
end

irb> fold_list([1,2,3]).to_a
=> [1, 3, 6]

链接已损坏(404)。 - amoebe
是的(有很多链接,但都失效了。我希望有人能找到镜像。) - Nakilon

1
尝试这段代码。
[1,2,3,4].inject([]){ |acc, value| acc << acc.last.to_i + value.to_i }

=> [1, 3, 6, 10]

1

还有一种方法(虽然我更喜欢khell的)

(1..10).inject([]) { |cs, i| cs << i + (cs.last || 0) }

我在发布我的答案后看到了hrnt发布的答案。虽然两种方法看起来一样,但上面的解决方案更高效,因为每个注入周期都使用相同的数组。
a,r = [1, 2, 3, 4],[]
k = a.inject(r) { |x, y| x + [(x.last || 0) + y] }
p r.object_id 
#  35742260
p k.object_id
#  35730450

你会注意到 r 和 k 是不同的。如果你对上面的解决方案进行相同的测试:

a,r = [1, 2, 3, 4],[]
k = a.inject(r) { |cs, i| cs << i + (cs.last || 0) }
p r.object_id
# 35717730
p k.object_id
# 35717730

变量 r 和 k 的对象 ID 是相同的。


1
可以改为 cs.last.to_i,这样可能更易读,但长度不会更短。 - TCSGrad

1
挖掘出这个方法,它可以 原地修改数组
class Array
  def cumulative_sum!
    (1..size-1).each {|i| self[i] += self[i-1] }
    self
  end
end

也可以泛化为:

  def cumulate!( &block )
    (1..size-1).each {|i| self[i] = yield self[i-1], self[i] }
    self
  end

  >> (1..10).to_a.cumulate! {|previous, next| previous * next }
  => [1, 2, 6, 24, 120, 720, 5040, 40320, 362880, 3628800]

0
我们需要的 Haskell 函数是 scanl,而不是 scanr
class Array
  def scanl(init)
    self.reduce([init]) { |a, e| a.push(yield(a.last, e)) }
  end
end

[1,2,3,4].scanl(0) { |a, b| a + b }[1..-1]
=> [1, 3, 6, 10]

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接