我正在阅读有关CodeAcademy的一些教程,并遇到了这种情况:
books = ["Charlie and the Chocolate Factory", "War and Peace", "Utopia", "A Brief History of Time", "A Wrinkle in Time"]
# To sort our books in ascending order, in-place
books.sort! { |firstBook, secondBook| firstBook <=> secondBook }
# Sort your books in descending order, in-place below
# this lin initially left blank
books.sort! {|firstBook, secondBook| secondBook <=> firstBook}
我没有使用if
/ else
块,而是给了它一个镜头,它有效,但我不知道为什么。我假设您将项目放在支票中的顺序无关紧要(即a <=> b
与b <=> a
)。有人能解释一下这里发生了什么吗?
答案 0 :(得分:5)
如果您反转<=>
中的元素,则会反转其值。如果元素相等,则此运算符返回0,但如果第一个较小则返回负值,如果第一个较大则返回正值。因此,如果temp = a <=> b
则b <=> a
为-temp
。因此,如果以相反的顺序编写参数,则反转排序顺序。
答案 1 :(得分:3)
以下是一些简单的视觉方式,可以看到<=>
的作用,以及如何反转比较变量的顺序会影响输出的顺序。
从基本数组开始:
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!
并不总是排序复杂对象的最快方法。简单的对象,如字符串和字符,以及数字,排序非常快,因为它们的类实现了快速执行<=>
测试的必要方法。
有些答案和评论提及sort_by
,所以让我们去那里。
复杂对象通常不能正确排序,因此我们最终使用getter / accessors来检索我们想要比较的值,并且该操作的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
和<=>
的否定值没有反应,因此,基于some benchmarks在Stack Overflow上做了一段时间,我们知道在结果值上使用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
次进行比较时很明显。在正确的时间使用正确的方法,你可以看到戏剧性的加速。
答案 2 :(得分:0)
Its called a spaceship operator
如果你有类似的东西
my_array = ["b","c","a"]
my_array.sort!
比较数组的元素,因为它知道英文字母的字母有自然顺序,同样如果你有整数数组
my_array2 = [3,1,2]
my_array2.sort!
将比较元素并将结果显示为[1,2,3]
但如果您想更改字符串数组或复杂对象的比较方式,请使用<=>
运算符指定它。
my_array3 = ["hello", "world how are" , "you"]
my_array3.sort! { |first_element, second_element| first_element <=> second_element }
所以它会告诉sort方法比较如下:
是first_element
&lt; second_element
?
first_element
= second_element
?
是first_element
&gt; second_element
?
但如果你采取这个措施,
my_array3.sort! { |first_element, second_element| first_element <=> second_element }
比较如下:
是second_element&lt; first_element?
是second_element = first_element?
是second_element&gt; first_element?
如果你改变要考虑的元素,它确实会有所作为。