这里有一些简单的可视化方法可以查看 <=> 的作用,以及颠倒比较变量的顺序如何影响输出的顺序。
从一个基本的数组开始:
foo = %w[a z b x]
我们可以进行升序排序:
foo.sort { |i, j| i <=> j } # => ["a", "b", "x", "z"]
或者通过反转被比较的两个变量进行降序排序:
foo.sort { |i, j| j <=> i } # => ["z", "x", "b", "a"]
<=> 运算符返回 -1、0 或 1,具体取决于比较分别是 <、== 或 >。
我们可以通过否定比较的结果来测试,如果理论成立,这将颠倒顺序。
foo.sort { |i, j| -(i <=> j) } # => ["z", "x", "b", "a"]
foo.sort { |i, j| -(j <=> i) } # => ["a", "b", "x", "z"]
通过否定比较的结果,顺序确实颠倒了。但是,为了代码清晰起见,只需颠倒变量的顺序即可。
总而言之,使用sort 或其破坏性兄弟sort! 并不总是对复杂对象进行排序的最快方法。简单对象(如字符串和字符以及数字)排序非常快,因为它们的类实现了快速执行<=> 测试所需的方法。
部分答案和cmets提到sort_by,那就去吧。
复杂对象通常不能正确排序,因此我们最终使用 getter/accessor 来检索我们想要比较的一些值,并且该操作会消耗 CPU 时间。 sort 反复比较这些值,以便反复进行检索,并在没有进行排序时累加起来。
为了解决这个问题,一位名叫 Randall Schwartz 的聪明人(他是 Perl 世界的主要参与者)开始使用一种算法,该算法可以预先计算一次用于排序的值;因此,该算法通常称为Schwartzian Transform。该值和实际对象被捆绑在一个小的子数组中,然后进行排序。因为排序是针对预先计算的值进行的,所以它和它的关联对象在排序中移动,直到排序完成。此时,实际对象被检索并作为方法的结果返回。 Ruby 使用 sort_by 实现这种类型的排序。
sort_by 不会在外部使用<=>,因此您可以通过简单地告诉它如何获得您想要比较的值来进行排序:
class Foo
attr_reader :i, :c
def initialize(i, c)
@i = i
@c = c
end
end
这是对象数组。请注意,它们按创建顺序排列,但未排序:
foo = [[1, 'z'], [26, 'a'], [2, 'x'], [25, 'b'] ].map { |i, c| Foo.new(i, c) }
# => [#<Foo:0x007f97d1061d80 @c="z", @i=1>,
# #<Foo:0x007f97d1061d58 @c="a", @i=26>,
# #<Foo:0x007f97d1061d30 @c="x", @i=2>,
# #<Foo:0x007f97d1061ce0 @c="b", @i=25>]
按整数值排序:
foo.sort_by{ |f| f.i }
# => [#<Foo:0x007f97d1061d80 @c="z", @i=1>,
# #<Foo:0x007f97d1061d30 @c="x", @i=2>,
# #<Foo:0x007f97d1061ce0 @c="b", @i=25>,
# #<Foo:0x007f97d1061d58 @c="a", @i=26>]
按字符值排序:
foo.sort_by{ |f| f.c }
# => [#<Foo:0x007f97d1061d58 @c="a", @i=26>,
# #<Foo:0x007f97d1061ce0 @c="b", @i=25>,
# #<Foo:0x007f97d1061d30 @c="x", @i=2>,
# #<Foo:0x007f97d1061d80 @c="z", @i=1>]
sort_by 对使用否定值(如 sort 和 <=>)的响应效果不佳,因此,基于不久前在 Stack Overflow 上完成的 some benchmarks,我们知道使用 reverse 在结果值是将顺序从升序切换到降序的最快方式:
foo.sort_by{ |f| f.i }.reverse
# => [#<Foo:0x007f97d1061d58 @c="a", @i=26>,
# #<Foo:0x007f97d1061ce0 @c="b", @i=25>,
# #<Foo:0x007f97d1061d30 @c="x", @i=2>,
# #<Foo:0x007f97d1061d80 @c="z", @i=1>]
foo.sort_by{ |f| f.c }.reverse
# => [#<Foo:0x007f97d1061d80 @c="z", @i=1>,
# #<Foo:0x007f97d1061d30 @c="x", @i=2>,
# #<Foo:0x007f97d1061ce0 @c="b", @i=25>,
# #<Foo:0x007f97d1061d58 @c="a", @i=26>]
它们在某种程度上可以互换,但您必须记住 sort_by 确实有开销,当您将其时间与运行简单对象时的 sort 时间进行比较时,这一点很明显。在正确的时间使用正确的方法,您会看到显着的加速。