25

我在 Ruby 中学习了两种数组排序方法:

array = ["one", "two", "three"]
array.sort.reverse!

或者:

array = ["one", "two", "three"]
array.sort { |x,y| y<=>x }

而且我无法区分两者。哪种方法更好,它们在执行上有何不同?

4

5 回答 5

33

这两行都是一样的(创建一个新的数组,它是反向排序的)。主要论点是关于可读性和性能。array.sort.reverse!array.sort{|x,y| y<=>x}-我认为我们可以在这里达成一致。

对于性能部分,我创建了一个快速基准测试脚本,它在我的系统上提供了以下内容 ( ruby 1.9.3p392 [x86_64-linux]):

                              user     system      total        real
array.sort.reverse        1.330000   0.000000   1.330000 (  1.334667)
array.sort.reverse!       1.200000   0.000000   1.200000 (  1.198232)
array.sort!.reverse!      1.200000   0.000000   1.200000 (  1.199296)
array.sort{|x,y| y<=>x}   5.220000   0.000000   5.220000 (  5.239487)

对于基准脚本的多次执行,运行时间是相当稳定的。

array.sort.reverse(有或没有!)比array.sort{|x,y| y<=>x}. 因此,我建议这样做。


这是作为参考的脚本:

#!/usr/bin/env ruby
require 'benchmark'

Benchmark.bm do|b|
  master = (1..1_000_000).map(&:to_s).shuffle
  a = master.dup
  b.report("array.sort.reverse      ") do
    a.sort.reverse
  end

  a = master.dup
  b.report("array.sort.reverse!     ") do
    a.sort.reverse!
  end

  a = master.dup
  b.report("array.sort!.reverse!    ") do
    a.sort!.reverse!
  end

  a = master.dup
  b.report("array.sort{|x,y| y<=>x} ") do
    a.sort{|x,y| y<=>x}
  end
end
于 2013-05-19T18:33:19.680 回答
7

这里真的没有区别。两种方法都返回一个新数组。

就本示例而言,越简单越好。我会推荐array.sort.reverse,因为它比替代方案更具可读性。sort应该为更复杂的数据结构和用户定义的类的数组保存将块传递给类似的方法。

编辑:虽然destructive方法(任何以 ! 结尾的东西)对性能游戏都有好处,但有人指出,它们不需要返回更新的数组,或者根本不需要返回任何东西。记住这一点很重要,因为array.sort.reverse!很可能会返回nil。如果您希望在新生成的数组上使用破坏性方法,您应该更喜欢.reverse!在单独的行上调用而不是使用单行。

例子:

array = array.sort
array.reverse!

应该优先于

array = array.sort.reverse!
于 2013-05-19T18:17:11.893 回答
3

撤销!是比较快的

基准测试通常没有替代品。虽然在较短的脚本中可能没有区别,但#reverse!方法比使用“宇宙飞船”运算符排序要快得多。例如,在 MRI Ruby 2.0 上,并给出以下基准代码:

require 'benchmark'

array = ["one", "two", "three"]
loops = 1_000_000

Benchmark.bmbm do |bm|
    bm.report('reverse!')  { loops.times {array.sort.reverse!} }
    bm.report('spaceship') { loops.times {array.sort {|x,y| y<=>x} }}
end

系统报告#reverse!几乎是使用组合比较运算符的两倍。

                user     system      total        real
reverse!    0.340000   0.000000   0.340000 (  0.344198)
spaceship   0.590000   0.010000   0.600000 (  0.595747)

我的建议:在给定的上下文中使用语义上更有意义的那个,除非你在一个紧密的循环中运行。

于 2013-05-19T18:53:11.437 回答
2

与您的示例一样简单的比较,没有太大区别,但是随着比较公式变得复杂,最好避免使用<=>with 块,因为您传递的块将针对数组的每个元素进行评估,从而导致冗余。考虑一下:

array.sort{|x, y| some_expensive_method(x) <=> some_expensive_method(y)}

在这种情况下,some_expensive_method将对 的每对可能的元素进行评估array

在您的特定情况下,<=>可以避免使用 with块reverse

array.sort_by{|x| some_expensive_method(x)}.reverse

这称为施瓦茨变换。

于 2013-05-19T18:25:24.033 回答
2

在我的机器上使用 tessi 的基准测试时,我得到了一些有趣的结果。我在ruby 2.0.0p195 [x86_64-darwin12.3.0]OS X 系统上运行,即最新版本的 Ruby 2。我使用的是 bmbm而不是bmBenchmark 模块。我的时间是:

Rehearsal -------------------------------------------------------------
array.sort.reverse:         1.010000   0.000000   1.010000 (  1.020397)
array.sort.reverse!:        0.810000   0.000000   0.810000 (  0.808368)
array.sort!.reverse!:       0.800000   0.010000   0.810000 (  0.809666)
array.sort{|x,y| y<=>x}:    0.300000   0.000000   0.300000 (  0.291002)
array.sort!{|x,y| y<=>x}:   0.100000   0.000000   0.100000 (  0.105345)
---------------------------------------------------- total: 3.030000sec

                                user     system      total        real
array.sort.reverse:         0.210000   0.000000   0.210000 (  0.208378)
array.sort.reverse!:        0.030000   0.000000   0.030000 (  0.027746)
array.sort!.reverse!:       0.020000   0.000000   0.020000 (  0.020082)
array.sort{|x,y| y<=>x}:    0.110000   0.000000   0.110000 (  0.107065)
array.sort!{|x,y| y<=>x}:   0.110000   0.000000   0.110000 (  0.105359)

首先,请注意,在排练阶段,sort!使用比较块是明显的赢家。Matz 一定在 Ruby 2 中调整了它!

我发现的另一件非常奇怪的事情是生产过程中有多少改进array.sort.reverse!array.sort!.reverse!展示。它是如此极端,以至于我怀疑我是否以某种方式搞砸并传递了这些已经排序的数据,因此我在执行每个基准测试之前添加了对排序或反向排序数据的显式检查。


我的 tessi 脚本变体如下:

#!/usr/bin/env ruby
require 'benchmark'

class Array
  def sorted?
    (1...length).each {|i| return false if self[i] < self[i-1] }
    true
  end

  def reversed?
    (1...length).each {|i| return false if self[i] > self[i-1] }
    true
  end
end

master = (1..1_000_000).map(&:to_s).shuffle

Benchmark.bmbm(25) do|b|
  a = master.dup
  puts "uh-oh!" if a.sorted?
  puts "oh-uh!" if a.reversed?
  b.report("array.sort.reverse:") { a.sort.reverse }

  a = master.dup
  puts "uh-oh!" if a.sorted?
  puts "oh-uh!" if a.reversed?
  b.report("array.sort.reverse!:") { a.sort.reverse! }

  a = master.dup
  puts "uh-oh!" if a.sorted?
  puts "oh-uh!" if a.reversed?
  b.report("array.sort!.reverse!:") { a.sort!.reverse! }

  a = master.dup
  puts "uh-oh!" if a.sorted?
  puts "oh-uh!" if a.reversed?
  b.report("array.sort{|x,y| y<=>x}:") { a.sort{|x,y| y<=>x} }

  a = master.dup
  puts "uh-oh!" if a.sorted?
  puts "oh-uh!" if a.reversed?
  b.report("array.sort!{|x,y| y<=>x}:") { a.sort!{|x,y| y<=>x} }
end
于 2013-05-20T03:03:50.560 回答