我在Ruby中学到了两种数组排序方法:
array = ["one", "two", "three"]
array.sort.reverse!
或:
array = ["one", "two", "three"]
array.sort { |x,y| y<=>x }
我无法区分这两者。哪种方法更好,它们在执行方面究竟有何不同?
答案 0 :(得分:31)
两行都是相同的(创建一个新的数组,反向排序)。主要论点是关于可读性和性能。 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
答案 1 :(得分:6)
这里确实没有区别。两种方法都返回一个新数组。
出于本示例的目的,更简单更好。我建议使用array.sort.reverse
,因为它比替代方案更具可读性。将块传递给sort
等方法应该保存为更复杂的数据结构和用户定义类的数组。
编辑:虽然destructive
方法(以!结尾的任何内容)对性能游戏都有好处,但有人指出它们不是必需来返回更新的数组,或任何东西完全没问题。记住这一点非常重要,因为array.sort.reverse!
很可能会返回nil
。如果您希望在新生成的数组上使用破坏性方法,则应该更喜欢在单独的行上调用.reverse!
,而不是使用单行。
示例:
array = array.sort
array.reverse!
应该优先于
array = array.sort.reverse!
答案 2 :(得分: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)
我的建议:在给定的上下文中使用更具语义意义的内容,除非你在紧密的循环中运行。
答案 3 :(得分:2)
通过比较和您的示例一样简单,没有太大的区别,但由于比较公式变得复杂,最好避免将<=>
与块一起使用,因为您传递的块将针对每个块进行评估数组的元素,导致冗余。考虑一下:
array.sort{|x, y| some_expensive_method(x) <=> some_expensive_method(y)}
在这种情况下,some_expensive_method
将针对array
的每个可能元素对进行评估。
在您的特定情况下,使用<=>
可以避免使用reverse
的阻止。
array.sort_by{|x| some_expensive_method(x)}.reverse
这称为Schwartzian变换。
答案 4 :(得分:2)
在我的机器上玩tessi的基准测试时,我得到了一些有趣的结果。我在OS X系统上运行ruby 2.0.0p195 [x86_64-darwin12.3.0]
,即最新版本的Ruby 2。我在Benchmark模块中使用了bmbm而不是bm
。我的时间是:
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