ruby's< =>运算符和排序方法

时间:2014-10-27 05:26:39

标签: ruby spaceship-operator

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有很多低级别的东西,我无法在高级别上看到我正在编程。这看起来很自然......但这种情况似乎特别从较低级别删除排序方法。排序如何使用太空船运营商?为什么以我们允许的方式重新定义太空船操作员现在可以对物体进行排序?

3 个答案:

答案 0 :(得分:41)

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

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

1)Ruby不能按照你对#34; sort&#34;这个词的思考方式进行排序。 Ruby只能交换&#39;数组元素,它可以比较&#39;数组元素。

2)Ruby使用一个称为宇宙飞船的比较运算符来对数字进行属性化,以帮助它排序&#39;。这些数字是-1,0,1。人们错误地认为这3个数字正在帮助它排序&#39; (例如,如果有一个包含3个数字的数组,如10,20,30,那么10将是-1,20是0,而30 a 1,而Ruby只是简化排序,将其减少为-1,0,1。这是错误的.Ruby无法排序&#34;它不能只进行比较。

看看宇宙飞船运营商。它将3个独立的运营商集中在一个,&lt;,the =和&gt;中。当Ruby比较两个变量时,它会产生其中一个变量。

Spaceship Operator

那说,&#34;结果&#34;意思?它DOESN&t; T表示其中一个变量被赋值为0,1,-1。它只是Ruby可以获取两个变量并使用它们执行某些操作的方式。现在,如果您只是运行:

puts 4 <=> 5

你得到-1的结果,因为无论什么&#39;部分&#39;比较运算符(宇宙飞船)的(例如,&lt;,=或&gt;)为真,得到分配给它的数字(如上图所示)。当Ruby看到这个&lt; =&gt;虽然有一个数组,它只对数组有两件事:保持数组单独或交换数组的元素。

如果Ruby使用&lt; =&gt;并获得1,它将交换数组的2个元素。如果Ruby得到-1或0的结果,它将使数组保持不变。

一个例子是Ruby看到数组[2,1]。排序方法将使它拉入这些数字,如2&lt; =&gt; 1。由于宇宙飞船的一部分(如果你想这样想的话)是真的是&gt; (即2> 1为真),结果为&#39; 1&#39;来自Ruby。当Ruby看到宇宙飞船的1个结果时,它交换数组的2个元素。现在数组是[1,2]。

希望在这一点上,您可以看到Ruby仅与&lt; =&gt;进行比较。运算符,然后交换(或单独留下)它比较的数组中的2个元素。

理解.sort方法是一种迭代方法,这意味着它是一种多次运行代码块的方法。大多数人只有在他们看到诸如.each或.upto之类的方法之后才会被介绍到.sort方法(如果你还没有听说过,那么你不需要知道那些做了什么) ,但这些方法只运行一次数组。 .sort方法的不同之处在于,它将根据需要多次运行您的数组,以便对其进行排序(按排序,我们的意思是比较和交换)。

确保您理解Ruby语法:

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

代码块(由{}&#39; s包围)是Ruby从最低到最高排序的任何方式。但足以说明.sort方法的第一次迭代将在管道(a,b)之间分配变量,即数组的前两个元素。因此,对于第一次迭代a = 4和b = 5,并且因为4 <5,这导致-1,Ruby认为它意味着不交换数组。它为第二次迭代执行此操作,意味着a = 5且b = 6,看到5 <6,导致-1并且单独留下阵列。由于所有&lt; =&gt;结果是-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看到b&lt; =&gt; a,并且说是1&lt; 5?是。结果为-1。保持原样。

迭代2:数组[5, 1,9 ]。 a = 1,b = 9。 Ruby看到b&lt; =&gt; a,并且说是9&lt; 1?不会导致1.交换2个数组元素。该数组现在是[5,9,1]

迭代3:数组[ 5,9 ,1]。从b / c开始,在完成所有操作之前,数组中有+1结果。 a = 5,b = 9。 Ruby看到b&lt; =&gt; a,即9&lt; 5?不,这导致1.交换。 [9,5,1]

迭代4:数组[9, 5,1 ]。 a = 5,b = 1。 Ruby看到b&lt; =&gt; a,即1&lt; 5?是。结果为-1。因此,不执行交换。完成。 [9,5,1]。

想象一个数组,前999个元素的数字为50,元素1000的数字为1。如果你意识到Ruby必须经历数千次这样的数组做同样的简单比较和交换,你完全理解排序方法例程将1一直转移到数组的开头。

现在,我们终于可以看到.sort来到一个对象。

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

现在应该更有意义了。在对象上调用.sort方法时,就像运行:

时一样
@players.sort

它拉出&#34; def&lt; =&gt;&#34;带有参数的方法(例如,&#39;其他&#39;),其中包含来自@players的当前对象(例如,&#39;无论当前实例对象是&#39; @ players&#39;,因为它& #39;是排序方法,它最终将通过&#39; @ players&#39;数组的所有元素。就像当你尝试在类上运行puts方法一样,它会自动调用该类中的to_s方法。自动查找&lt; =&gt;的.sort方法也是如此。方法

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

作为最后的花絮,Ruby通过将其转换为数值来按字母顺序排序。它只考虑从ASCII分配代码的任何字母(意味着因为大写字母在ASCII代码图表上具有较低的数值,所以大写将默认排序为第一个。)

希望这有帮助!

答案 1 :(得分:3)

在你的例子中

@players.sort

相当于

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

根据<=>方法的返回对元素进行排序。如果<=>返回-1,则第一个元素在第二个元素之前排序,如果它返回1,则第二个元素在第一个元素之前排序。如果您更改返回值(例如交换元素),则根据返回值更改订单。

答案 2 :(得分:1)

sort实际上是一个依赖于<=>实现的Enumerable方法。来自Ruby doc本身:

  

如果使用Enumerable#max,#min或#sort,则表示对象   集合还必须实现有意义的&lt; =&gt;运营商,就像这些   方法依赖于集合成员之间的排序。

亲自尝试:

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

你知道,当我们在sort数组上使用@players时,Player的对象会被<=>调用,如果你没有实现它,那么你&#39 ; ll可能会得到:

  

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

这是有道理的,因为对象并不知道如何处理<=>

相关问题