【问题标题】:How to quickly multiply the two largest array elements如何快速将两个最大的数组元素相乘
【发布时间】:2016-12-12 22:02:54
【问题描述】:

我正在做一个挑战,它要求一种方法将数组的两个最大元素相乘并找到一个耗时不到一秒的解决方案。

这是我目前大约 1.6 秒的时间

def max_product(a)
  a.sort[-1] * a.sort[-2]
end 

我怎样才能重写它以加快速度?

【问题讨论】:

标签: arrays ruby performance methods


【解决方案1】:
a = [3,4,2,5,2,6]

def max_product(a)
  a.max(2).reduce(:*)
end

max_product(a)
  #=> 30

Enumerable#max 在 Ruby v.2.2 中被允许有一个参数。 minmax_bymin_by 相同。

请注意,Enumerable#max 将受益于即将推出的 Ruby v2.4 的性能改进。

正如@ZbyszekKr 建议的那样,让我们​​将其与仅排序和获取最后两个值以及滚动你自己的值进行比较。

def max_product_sort(a)
  a.sort.last(2).inject(:*)
end

def max_product_sort!(a)
  a.sort!
  a[-2] * a[-1]
end

def max_product_rolled(arr)
  m1 = arr.max
  max_loc = arr.index(m1)
  arr[max_loc] = arr[0,2].min - 1
  m2 = arr.max
  arr[max_loc] = m1 # to avoid mutating arr
  m1 * m2
end

首先让我们使用fruity gem 进行比较。

require 'fruity'

arr = 1_000_000.times.map { rand 2_000_000 }
arr1 = arr.dup
arr2 = arr.dup
arr3 = arr.dup
arr4 = arr.dup

compare(
  max_2:  -> { max_product(arr1) },
  rolled: -> { max_product_rolled(arr2) },
  sort:   -> { max_product_sort(arr3) },
  sort!:  -> { max_product_sort!(arr4) }
)
Running each test once. Test will take about 8 seconds.
sort! is faster than max_2 by 4x ± 0.1
max_2 is faster than rolled by 2x ± 0.1
rolled is faster than sort by 2.1x ± 0.1

接下来使用benchmark进行比较。

arr = 1_000_000.times.map { rand 2_000_000 }
arr1 = arr.dup
arr2 = arr.dup
arr3 = arr.dup
arr4 = arr.dup

require 'benchmark'

Benchmark.bm do |x|
  x.report("max_2")  { max_product(arr1) }
  x.report("rolled") { max_product_rolled(arr2) }
  x.report("sort")   { max_product_sort(arr3) }
  x.report("sort!")  { max_product_sort!(arr4) }
end

          user      system     total       real
max_2   0.060000   0.010000   0.070000 (  0.066777)
rolled  0.110000   0.000000   0.110000 (  0.111191)
sort    0.210000   0.000000   0.210000 (  0.218155)
sort!   0.210000   0.010000   0.220000 (  0.214664)

最后,让我们尝试benchmark 进行热身。我们不能在此测试中包含 sort !,因为数组将在预热时就地排序,使其在重要的测试中超快。

arr = 1_000_000.times.map { rand 2_000_000 }
arr1 = arr.dup
arr2 = arr.dup
arr3 = arr.dup

Benchmark.bmbm do |x|
  x.report("max_2")  { max_product(arr1) }
  x.report("rolled") { max_product_rolled(arr2) }
  x.report("sort")   { max_product_sort(arr3) }
end

Rehearsal ------------------------------------------
max_2    0.060000   0.000000   0.060000 (  0.066969)
rolled   0.110000   0.000000   0.110000 (  0.117527)
sort     0.210000   0.020000   0.230000 (  0.244783)
--------------------------------- total: 0.400000sec

             user     system      total        real
max_2    0.050000   0.000000   0.050000 (  0.059948)
rolled   0.100000   0.000000   0.100000 (  0.106099)
sort     0.200000   0.000000   0.200000 (  0.219202)

如您所见,benchmark 结果与使用fruity 获得的结果不同,sort!benchmark 中排在最后,在fruity 中排在首位。我想我知道为什么sort!fruity 中看起来那么好。 fruitygithub page 声明,“我们首先确定获得有意义的时钟测量所需的内部迭代次数......”我怀疑,对于 sort!,这个初始步骤会改变 arr4,扭曲结果以下报告的测试。

不管怎样,benchmark 的结果符合我的预期(除了sortsort! 稍快一点。

【讨论】:

  • 这可能是最好的解决方案。对数组进行排序的其他中继是 O(n*log(n))。该算法有机会在线性时间内工作。
  • 可能是 max 是通过排序实现的,这有点令人印象深刻。您总是可以自己进行线性搜索。
  • 你不应该在基准测试之前克隆arr吗?否则,您可能会更改结果,因为排序是在原地完成的。
  • 好的,现在你肯定会在同一个数组上测试sort 之前通过sort! 弄乱结果。
  • @pjs,我注意到编辑时,而不是克隆我决定将最大值更改为小于两个最大值的值,再次获得最大值然后恢复原来的警报最大值以避免改变数组。
猜你喜欢
  • 1970-01-01
  • 2017-02-02
  • 2013-11-08
  • 1970-01-01
  • 2012-07-25
  • 1970-01-01
  • 1970-01-01
  • 2017-01-08
  • 1970-01-01
相关资源
最近更新 更多