ruby的运算符和排序方法

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

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

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

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

2)Ruby使用一个称为宇宙飞船的比较运算符来对数字进行归属以帮助它“排序”。 这些数字是-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]。 排序方法会使它像2 <=> 1一样拉入这些数字。 由于太空船的一部分(如果你想这样想的话)是真的是>(即2> 1是真的),结果是来自Ruby的’1’。 当Ruby看到宇宙飞船的1个结果时,它会交换数组的2个元素。 现在数组是[1,2]。

希望在这一点上,你看到Ruby只与<=>运算符进行比较,然后交换(或单独留下)它所比较的​​数组中的2个元素。

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

要确保您了解Ruby语法:

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

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

迭代2:数组[ 5,19 ]。 a = 1,b = 9。 Ruby看到b <=> a,并说9 <1? 不会。结果是1.交换2个数组元素。 阵列现在是[5,9,1]

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

迭代4:数组[ 9,5,1 ]。 a = 5,b = 1。 Ruby看到b <=> a,说是1 <5? 是。 这导致-1。 因此,不执行交换。 完成。 [9,5,1]。

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

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

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

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

 @players.sort 

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

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

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

希望这可以帮助!

在你的例子中

 @players.sort 

相当于

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

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

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

如果使用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'" #=> [#, #, #] 

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

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

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