【问题标题】:How does <=> work for different sorting strategies?<=> 如何适用于不同的排序策略?
【发布时间】:2013-12-13 08:49:47
【问题描述】:

我正在浏览 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 &lt;=&gt; bb &lt;=&gt; a)。有人可以解释这里发生了什么吗?

【问题讨论】:

  • 如果您反转项目,那么排序会更改顺序,如降序和升序
  • HOPE Ruby 教程不建议您使用 camelCase 作为变量。在 Ruby 中,我们使用 snake_case 作为变量。
  • 我不记得了——这只是个人喜好。我真的不喜欢在变量名中看到 _。

标签: ruby


【解决方案1】:

如果你颠倒&lt;=&gt; 中的元素,你就会颠倒它的值。如果元素相等,则此运算符返回 0,但如果第一个较小,则返回负值,如果第一个较大,则返回正值。因此如果temp = a &lt;=&gt; b 那么b &lt;=&gt; a 就是-temp。因此,如果以相反的顺序编写参数,则可以颠倒排序顺序。

【讨论】:

  • 谢谢,但我还是有点困惑——也许我的问题应该是,-1 vs 1 的意义是什么?我知道 -1 小于对象而 1 大于对象,但是一旦我们传递了该值,排序策略如何处理它?
  • @cote 所谓的宇宙飞船操作员&lt;=&gt; 是基本比较器。它用于确定两个元素相对于另一个元素应具有的排序顺序。如果-1 那么第一个在第二个之前,如果1 那么第一个在第二个之后。如果0 从排序的角度来看它们被认为是完全相同的并且不应用排序。
  • @ctote 假设你有一个排序算法,如果a 小于b,它会做一些事情。如果您颠倒此运算符的参数顺序,算法将执行相同的操作,但其 a 小于 b 的概念将被颠倒。因此,每当a 在第一种情况下小于b 时,在第二种情况下它会变得更大。试着写下一个简单的冒泡排序。如果你的比较 a &lt; b 被颠倒了会发生什么,即返回 true iff a &gt; b
  • @ctote 如果你感到困惑,那么尽量不要使用它,而使用sort_by!,它使用了施瓦茨变换。这样更容易理解,效率更高。降序可以通过reverse 来实现。没有太多理由使用sort!
  • sort_by 在对复杂对象进行排序时效率更高。简单的对象,如字符串和整数,使用sort 会更快。那是因为sort_by 有开销来执行施瓦茨变换。它被方法名称所掩盖,但查看底层代码,您会发现正在排序的元素的设置和拆卸具有相关的成本。
【解决方案2】:

这里有一些简单的可视化方法可以查看 &lt;=&gt; 的作用,以及颠倒比较变量的顺序如何影响输出的顺序。

从一个基本的数组开始:

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"]

&lt;=&gt; 运算符返回 -1、0 或 1,具体取决于比较分别是 &lt;==&gt;

我们可以通过否定比较的结果来测试,如果理论成立,这将颠倒顺序。

foo.sort { |i, j| -(i <=> j) } # => ["z", "x", "b", "a"]
foo.sort { |i, j| -(j <=> i) } # => ["a", "b", "x", "z"]

通过否定比较的结果,顺序确实颠倒了。但是,为了代码清晰起见,只需颠倒变量的顺序即可。

总而言之,使用sort 或其破坏性兄弟sort! 并不总是对复杂对象进行排序的最快方法。简单对象(如字符串和字符以及数字)排序非常快,因为它们的类实现了快速执行&lt;=&gt; 测试所需的方法。

部分答案和cmets提到sort_by,那就去吧。

复杂对象通常不能正确排序,因此我们最终使用 getter/accessor 来检索我们想要比较的一些值,并且该操作会消耗 CPU 时间。 sort 反复比较这些值,以便反复进行检索,并在没有进行排序时累加起来。

为了解决这个问题,一位名叫 Randall Schwartz 的聪明人(他是 Perl 世界的主要参与者)开始使用一种算法,该算法可以预先计算一次用于排序的值;因此,该算法通常称为Schwartzian Transform。该值和实际对象被捆绑在一个小的子数组中,然后进行排序。因为排序是针对预先计算的值进行的,所以它和它的关联对象在排序中移动,直到排序完成。此时,实际对象被检索并作为方法的结果返回。 Ruby 使用 sort_by 实现这种类型的排序。

sort_by 不会在外部使用&lt;=&gt;,因此您可以通过简单地告诉它如何获得您想要比较的值来进行排序:

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&lt;=&gt;)的响应效果不佳,因此,基于不久前在 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 时间进行比较时,这一点很明显。在正确的时间使用正确的方法,您会看到显着的加速。

【讨论】:

    【解决方案3】:

    Its called a spaceship operator

    如果你有这样的事情

    my_array = ["b","c","a"]

    my_array.sort! 会比较数组的元素,因为它知道英文字母具有自然顺序,如果你有整数数组

    my_array2 = [3,1,2]

    my_array2.sort! 将比较元素并给出结果为 [1,2,3]

    但如果您想更改在字符串数组或复杂对象中进行比较的方式,您可以使用&lt;=&gt; 运算符指定它..

    my_array3 = ["hello", "world how are" , "you"]

    my_array3.sort! { |first_element, second_element| first_element &lt;=&gt; second_element }

    所以它会告诉排序方法像这样比较:

    first_element second_element?

    first_element = second_element

    first_element > second_element

    但如果你采取这个 stmt,

    my_array3.sort! { |first_element, second_element| first_element &lt;=&gt; second_element }

    对比如下:

    第二个元素是

    是 second_element = first_element 吗?

    是 second_element > first_element 吗?

    因此,如果您更改要考虑的元素,确实会有所不同。

    【讨论】:

    • 通常被称为“宇宙飞船操作员”。在 Perl 中,它始终是一个二元比较运算符,而这正是 Ruby 继承它的地方。
    • 另外,在 Ruby 中对变量使用snake_case,而不是camelCase。而且,您的示例不起作用。使用first_elementsecond_element 并确保你的赋值和变量拼写是一致的。使用 IRB 测试您的示例是个好主意,然后在它们工作后复制并粘贴它们。
    • "...一个字符串数组或复杂对象然后它不知道如何比较这些对象"? Ruby 知道如何比较字符串,就像它处理单个字符(即字符串)一样。 %w[foo bar].sort # =&gt; ["bar", "foo"].
    猜你喜欢
    • 2019-04-20
    • 1970-01-01
    • 2019-07-03
    • 2020-07-10
    • 2017-04-09
    • 1970-01-01
    • 1970-01-01
    • 2019-04-30
    • 1970-01-01
    相关资源
    最近更新 更多