【问题标题】:ruby's <=> operator and sort methodruby 的 <=> 运算符和排序方法
【发布时间】:2014-12-22 06:44:25
【问题描述】:
player1 = Player.new("moe")
player2 = Player.new("larry",60)
player3 = Player.new("curly", 125)
@players = [player1, player2, player3]

在上面,我创建了一些播放器对象并将它们添加到之前为空的数组@players。

然后,我将 重新定义为:

def <=>(other)
    other.score <=> score
end 

然后我可以运行这段代码

@players.sort

我在@players 中的玩家对象数组从高分到低分排序。 我想这对我来说看起来有点黑。我有点不清楚这里发生了什么。我如何找出幕后发生的事情?

我所知道的是,如果你取两个值并使用宇宙飞船运算符/一般比较运算符:

2 <=> 1   
=> 1

1 <=> 2
=> -1

1 <=> 1
=>0

有时,似乎 Ruby 有很多低级的东西正在发生,而我在高级编程中看不到。这看起来很自然......但这种情况似乎特别从低级删除排序方法的事情。 sort 如何使用 spaceship 算子?为什么以我们现在所做的方式重新定义宇宙飞船运算符允许我们对对象进行排序?

【问题讨论】:

标签: ruby spaceship-operator


【解决方案1】:

在您了解排序对象之前。您需要了解 Ruby 中的 .sort 方法。如果您要对 5 张带有数字的卡片进行排序,您可以查看所有卡片,轻松找到最低的一张,然后选择那张作为您的第一张卡片(假设您是从最低到最高排序,Ruby做)。当您的大脑进行排序时,它可以查看所有内容并从那里进行排序。

这里有两个主要的混淆因素很少被解决:

1) Ruby 无法按照您对“排序”一词的看法进行排序。 Ruby 只能“交换”数组元素,它可以“比较”数组​​元素。

2) Ruby 使用了一个比较运算符,称为 spaceship,对数字进行属性化以帮助它“排序”。这些数字是-1,0,1。人们错误地认为这 3 个数字正在帮助它“排序”(例如,如果有一个包含 3 个数字的数组,例如 10、20、30,那么 10 将是 -1,20 是 0,30 是 1 ,而Ruby只是通过将排序减少到-1,0,1来简化排序。这是错误的。Ruby不能“排序”。它不能只比较)。

看看飞船操作员。它将 3 个单独的运算符合并为一个,即 。当 Ruby 比较两个变量时,它会产生这些数字之一。

也就是说,“结果”是什么意思?这并不意味着变量之一被分配了 0,1,-1。这只是 Ruby 可以使用两个变量并对其进行处理的一种方式。现在,如果你只是运行:

puts 4 <=> 5

你会得到 -1 的结果,因为无论比较运算符(宇宙飞船)的“部分”(例如 )是否为真,都会得到分配给它的数字(如在上图)。但是,当 Ruby 看到这个 带有一个数组时,它只会对数组做两件事:不理会数组或交换数组的元素。

如果 Ruby 使用 并得到 1,它将交换数组的 2 个元素。如果 Ruby 得到 -1 或 0 的结果,它将不理会数组。

例如,如果 Ruby 看到数组 [2,1]。排序方法会使其像 21 一样拉入这些数字。由于宇宙飞船的部分(如果你想这样想的话)是 > (即 2>1 是真的),结果是来自 Ruby 的“1”。当 Ruby 从宇宙飞船中看到 1 结果时,它交换 数组的 2 个元素。现在数组是 [1,2]。

希望此时,您会看到 Ruby 仅与 运算符进行比较,然后交换(或不理会)它比较的数组中的 2 个元素。

了解 .sort 方法是一种迭代方法,这意味着它是一个多次运行一段代码的方法。大多数人只有在看到诸如 .each 或 .upto 之类的方法后才被介绍给 .sort 方法(如果您没有听说过它们,则无需知道它们的作用),但是这些方法贯穿始终阵列仅 1 次。 .sort 方法的不同之处在于它会根据需要多次遍历您的数组,以便对其进行排序(通过排序,我们的意思是比较和交换)。

为了确保您了解 Ruby 语法:

foo = [4, 5, 6]
puts foo.sort {|a,b| a <=> b}

代码块(由 {} 包围)是 Ruby 从最低到最高排序时会做的任何事情。但只要说 .sort 方法的第一次迭代将在管道 (a, b) 之间分配数组的前两个元素就足够了。因此,对于第一次迭代 a=4 和 b=5,并且由于 4 结果都是 -1,Ruby 停止循环并感觉数组在 [4,5,6] 处排序。

我们可以通过简单地交换变量的顺序来从高到低排序。

bar = [5, 1, 9]
puts bar.sort {|a,b| b <=> a}   

这是 Ruby 正在做的事情:

迭代 1:数组 [5,1,9]。 a=5,b=1。 Ruby 看到 ba,然后说是 1

迭代 2:数组 [5,1,9]。 a=1,b=9。 Ruby 看到 ba,然后说是 9

迭代 3:数组 [5,9,1]。从 b/c 开始,在完成所有操作之前,数组中有一个 +1 结果。 a=5,b=9。 Ruby 看到 ba,说是 9

迭代 4:数组 [9,5,1]。 a=5,b=1。 Ruby 看到 ba,说是 1

想象一个数组,其中前 999 个元素的数字为 50,元素 1000 的数字为 1。如果您意识到 Ruby 必须通过该数组进行数千次相同的简单比较和交换,那么您就完全理解了排序方法例程将1 一直移动到数组的开头。

现在,我们终于可以看一下对象的 .sort 了。

def <=>(other)
    other.score <=> score
end 

这现在应该更有意义了。当对象上的 .sort 方法被调用时,比如你运行的时候:

@players.sort

它使用具有来自@players 的当前对象的参数(例如'other')提取“def ”方法(例如'无论当前实例对象是'@players',因为它是排序方法,它最终会遍历 '@players' 数组的所有元素)。这就像当您尝试在一个类上运行 puts 方法时,它会自动调用该类内部的 to_s 方法。 .sort 方法自动查找 方法也是如此。

查看 方法内部的代码,该类中必须有一个 .score 实例变量(带有访问器方法)或只是一个 .score 方法。 .score 方法的结果应该(希望)是一个字符串或数字——红宝石可以“排序”的两件事。如果它是一个数字,那么 Ruby 使用它的 'sort' 操作来重新排列所有这些对象,现在它知道要对这些对象的哪一部分进行排序(在这种情况下,它是 .score 方法或实例变量的结果)。

作为最后的花絮,Ruby 也通过将其转换为数值来按字母顺序排序。它只考虑为任何字母分配 ASCII 码(意思是由于大写字母在 ASCII 码表上的数值较低,默认情况下大写字母排在第一位)。

希望这会有所帮助!

【讨论】:

  • 超级解说很喜欢。
  • 你忘了一件事:要在某个类中使用 方法,你应该在其中包含 Comparable 模块。
【解决方案2】:

在你的例子中

@players.sort

等价于

@players.sort { |x, y| x <=> y }

根据&lt;=&gt; 方法的返回对元素进行排序。如果&lt;=&gt; 返回-1,则第一个元素排在第二个之前,如果返回1,则第二个排在第一个之前。如果您更改返回值(例如交换元素),则顺序会根据返回值更改。

【讨论】:

    【解决方案3】:

    sort 实际上是一个 Enumerable 方法,它依赖于&lt;=&gt; 的实现。来自 Ruby 文档本身:

    如果使用 Enumerable#max、#min 或 #sort,则 集合还必须实现一个有意义的 运算符,因为这些 方法依赖于集合成员之间的排序。

    自己试试吧:

    class Player
      attr_accessor :name, :score
    
      def initialize(name, score=0)
        @name = name
        @score = score  
      end
    
      def <=> other
        puts caller[0].inspect
        other.score <=> score
      end
    end
    
    player1 = Player.new("moe")
    player2 = Player.new("larry",60)
    player3 = Player.new("curly", 125)
    @players = [player1, player2, player3]
    puts @players.sort.inspect
    
    #=> "player.rb:19:in `sort'"
    #=> "player.rb:19:in `sort'"
    #=> [#<Player:0x007fe87184bbb8 @name="curly", @score=125>, #<Player:0x007fe87184bc08 @name="larry", @score=60>, #<Player:0x007fe87184bc58 @name="moe", @score=0>]
    

    你看,当我们在@players数组上使用sort时,Player的对象是用&lt;=&gt;调用的,如果你不实现它,那么你可能会得到:

    player.rb:14:in sort': comparison of Player with Player failed (ArgumentError) from player.rb:14:in'

    这是有道理的,因为对象不知道如何处理&lt;=&gt;。

    【讨论】:

      猜你喜欢
      • 2011-01-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-10-25
      • 1970-01-01
      • 1970-01-01
      • 2013-04-29
      • 2017-12-02
      相关资源
      最近更新 更多