Ruby/Rails中sum或reduce(:+)更好吗?除速度外还有其他考虑因素吗?

Leo*_*own 2 ruby ruby-on-rails

对于长数组来说,似乎#sum比#reduce快,而对于短数组,它们基本相同.

def reduce_t(s,f)
  start = Time.now
  puts (s..f).reduce(:+) #Printing the result just to make sure something is happening.
  finish = Time.now
  puts finish - start
end
def sum_t(s,f)
  start = Time.now
  puts (s..f).sum
  finish = Time.now
  puts finish - start
end

irb(main):078:0> sum_t(1,10); reduce_t(1,10)
55
0.000445
55
0.000195
=> nil
irb(main):079:0> sum_t(1,1000000); reduce_t(1,1000000)
500000500000
8.1e-05
500000500000
0.101487
=> nil
Run Code Online (Sandbox Code Playgroud)

除速度外还有其他考虑因素吗?有没有什么情况下使用#reduce而不是#sum来完成相同的结束,这是一个简单的总和?

编辑

mu太短了正确指出我应该在得出关于时序结果的结论之前进行多次迭代.我没有使用,Benchmark因为我还不熟悉它,但我希望我在下面写的内容足够且令人信服.

def sum_reduce_t(s,f)
  time_reduce = 0
  time_sum = 0
  reduce_faster = 0
  sum_faster = 0
  30.times do
    start_reduce = Time.now
    (s..f).reduce(:+)
    finish_reduce = Time.now
    time_reduce += (finish_reduce - start_reduce)
    start_sum = Time.now
    (s..f).sum
    finish_sum = Time.now
    time_sum += (finish_sum - start_sum)
    if time_sum > time_reduce
      reduce_faster += 1
    else
      sum_faster += 1
    end
  end
  puts "Total time (s) spent on reduce: #{time_reduce}"
  puts "Total time (s) spent on sum: #{time_sum}"
  puts "Number of times reduce is faster: #{reduce_faster}"
  puts "Number of times sum is faster: #{sum_faster}"
end

irb(main):205:0> sum_reduce_t(1,10)
Total time (s) spent on reduce: 0.00023900000000000004
Total time (s) spent on sum: 0.00015400000000000003
Number of times reduce is faster: 0
Number of times sum is faster: 30
=> nil
irb(main):206:0> sum_reduce_t(1,100)
Total time (s) spent on reduce: 0.0011480000000000004
Total time (s) spent on sum: 0.00024999999999999995
Number of times reduce is faster: 0
Number of times sum is faster: 30
=> nil
irb(main):207:0> sum_reduce_t(1,1000)
Total time (s) spent on reduce: 0.004804000000000001
Total time (s) spent on sum: 0.00019899999999999996
Number of times reduce is faster: 0
Number of times sum is faster: 30
=> nil
irb(main):208:0> sum_reduce_t(1,10000)
Total time (s) spent on reduce: 0.031862
Total time (s) spent on sum: 0.00010299999999999996
Number of times reduce is faster: 0
Number of times sum is faster: 30
=> nil
irb(main):209:0> sum_reduce_t(1,100000)
Total time (s) spent on reduce: 0.286317
Total time (s) spent on sum: 0.00013199999999999998
Number of times reduce is faster: 0
Number of times sum is faster: 30
=> nil
irb(main):210:0> sum_reduce_t(1,1000000)
Total time (s) spent on reduce: 2.7116779999999996
Total time (s) spent on sum: 0.00021200000000000008
Number of times reduce is faster: 0
Number of times sum is faster: 30
=> nil    
Run Code Online (Sandbox Code Playgroud)

我的问题仍然存在:是否有时候使用#reduce代替#sum?

Leo*_*own 5

好的,我认为这些评论可以结合起来形成一个很好的答案,但如果有人想更深入地解释,我当然会接受其他人。

关键点是:

  • sum 方法在当前版本的 Ruby 中不可用,但预计在未来可用。我通过 Rails 使用它,在那里它可用。
  • sum 方法对于对巨大数组求和确实更快。
  • 除非存在与极长数组相关的性能问题,否则将旧代码从 reduce 更改为 sum 可能不值得。
  • “鉴于 Array#sum 是在 MRI 中直接用 C 实现的,因此它更快也就不足为奇了。” (亩太短)
  • Reduce 还可以做许多其他事情(reduce(:-)reduce(:*)等),因此不应认为它无用。如果 sum 方法可用,它就不是对巨大数组求和的最佳选择。
  • 最重要的一点,我认为:如果你需要你的代码与早期版本的 Ruby 兼容,reduce 更安全。
  • “当您添加大量小值时,总和确实会增加,而注入 &:+ 不会发生这种情况。” (@马特)
  • reduce 方法可以产生一些有趣的表情符号。

我的问题的直接答案是:

  • 除了速度还有其他考虑吗?(兼容性,将非常大的数字添加到非常小的数字)。
  • 是否有任何情况下最好使用 #reduce 而不是 #sum 来完成相同的目的,一个简单的总和? (兼容性)。


mat*_*att 5

使用行为和结果sum不同的一种方法inject &:+是在对浮点值求和时.

如果向较小的浮点值添加大浮点值,通常结果与较大的浮点值相同:

> 99999999999999.98 + 0.001
=> 99999999999999.98
Run Code Online (Sandbox Code Playgroud)

添加浮点数时,这可能会导致错误,因为较小的值实际上会丢失,即使它们很多也是如此.

例如:

> a = [99999999999999.98, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001]
=> [99999999999999.98, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001]
> a.inject(&:+)
=> 99999999999999.98
Run Code Online (Sandbox Code Playgroud)

在此示例中,您可以0.001根据需要随时添加,它永远不会更改结果的值.

Ruby的实现sum在求和浮点数时使用Kahan求和算法来减少这个错误:

> a.sum
=> 100000000000000.0
Run Code Online (Sandbox Code Playgroud)

(注意这里的结果,你可能会期待一些结果,.99因为0.001数组中有10个.这只是正常的浮点行为,也许我应该试图找到一个更好的例子.重要的一点是总和确实增加了你添加了很多小的值,这不会发生inject &:+.)