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

时间:2016-10-06 18:51:20

标签: ruby-on-rails ruby

对于长数组,似乎#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

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

修改

mu is too short正确地指出,在得出关于时间结果的结论之前,我应该进行多次迭代。我没有使用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    

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

2 个答案:

答案 0 :(得分:5)

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

如果您将一个大的浮点值添加到一个小浮点值,通常结果与较大的浮点值相同:

> 99999999999999.98 + 0.001
=> 99999999999999.98

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

例如:

> 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

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

Ruby’s implementation of sum在对浮点数求和时使用Kahan summation algorithm来减少此错误:

> a.sum
=> 100000000000000.0

(注意结果在这里,你可能期望在.99中结束,因为数组中有10 0.001。这只是正常的浮点行为,也许我应该试着找到一个更好的例子。重要的一点是,当你添加许多小值时总和会增加,inject &:+不会发生这种情况。)

答案 1 :(得分:1)

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

关键点是:

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

我的问题的直接答案是:

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