Ruby的“<=>”(太空船)运算符是什么?

301

什么是 Ruby 的 <=>(太空船)运算符?其他编程语言是否实现了这个运算符?


1
现在怎么比较数组呢? 书上说“按元素逐个比较,如果相等返回0,如果小于返回-1,如果大于返回1”,但是对于[1,3,2] <=> [2,2,2]怎么处理? - SF.
3
@SF,当人们比较数组时,通常是按字典顺序比较(就像在词典中一样,即[1,3,2]<[2,2,2],因为第一个元素不同)。很少情况下(例如在Matlab中),数组比较会返回每个元素的结果数组;在这种情况下:[-1,1,0]。 - liori
请注意,如果包含 nil 元素的数组在任何 nil 元素之前的元素不同,则它们是可比较的;如果必须将 nil 与非 nil 进行比较,则它们是不可比较的。即 [1, nil] <=> [2, 3] => -1,但 [1, nil] <=> [1, 3] => nil。基本上这很糟糕。 - cliffordheath
当比较像 [1,nil] <=> [1,3] 这样的数组时,由于算法的一致性,你会得到一个 nil,它会逐个比较每个元素,直到 <=> 的结果不是 0。在这个例子中,Ruby 没有办法声明小于或大于,因为根本无法进行比较。应该将 nil 视为“不相等”。如果您了解数据的某些信息,并且想将 nil 视为 0,那么 Ruby 可以轻松实现。 - lilole
1
@liori 你应该使用例如 ("exempli gratia" 在英文中是"for example") 而不是 f.e. 我认为这更清晰并且更广泛被接受(在其他拉丁语言中也适用)。 - Aiden Cullo
6个回答

433

太空船操作符将根据左边参数与右边参数的大小关系返回10−1

a <=> b :=
  if a < b then return -1
  if a = b then return  0
  if a > b then return  1
  if a and b are not comparable then return nil

它通常用于数据排序。

它也被称为“三路比较运算符”。Perl 可能是第一个使用它的语言。还有其他支持它的语言,如 Apache Groovy、PHP 7+ 和 C++20。


30
没问题。我认为它是Java中Comparable非常优雅的版本。 - Mike Reedell
12
C#中的“analog”是指“IComparable.CompareTo”。 - Sergey Mirvoda
2
实际上,我认为可以返回任何负值或正值。0仍然表示相等。 - superluminary
3
请注意,如果比较的两个对象不可比较,则会得到一个 nil。 - gamov
@TonyArra:看起来C++20至少允许实现使用非1为基础的整数,只观察符号。我不知道这是否真的会发生,只是为了完整性而分享。请参见Herb Sutter的“Consistent comparison”第12页上的“Efficiency”注释--http://open-std.org/JTC1/SC22/WG21/docs/papers/2017/p0515r0.pdf。 - lindes

81

当您在自己的类中定义并包含Comparable模块时,宇宙飞船方法很有用。然后您的类将免费获得>, < , >=, <=, ==和between?等方法。

class Card
  include Comparable
  attr_reader :value

  def initialize(value)
    @value = value
  end

  def <=> (other) #1 if self>other; 0 if self==other; -1 if self<other
    self.value <=> other.value
  end

end

a = Card.new(7)
b = Card.new(10)
c = Card.new(8)

puts a > b # false
puts c.between?(a,b) # true

# Array#sort uses <=> :
p [a,b,c].sort # [#<Card:0x0000000242d298 @value=7>, #<Card:0x0000000242d248 @value=8>, #<Card:0x0000000242d270 @value=10>]

20

这是一个通用的比较运算符。它根据其接收方是否小于、等于或大于其参数返回-1、0或+1。


20

我将以简单的例子来解释

  1. [1,3,2] <=> [2,2,2]

    Ruby将从左侧开始比较两个数组的每个元素。 左侧数组的1小于右侧数组的2。因此,左侧数组小于右侧数组。输出为-1

  2. [2,3,2] <=> [2,2,2]

    与上面相似,它将首先比较相等的第一个元素,然后将比较第二个元素,在这种情况下,左侧数组的第二个元素大,因此输出为1


它只比较每个数组的第一个左元素还是会继续比较其他元素?很好的解释。 - Kick Buttowski
3
除非找到一个不相同的数字,否则它会继续比较其他元素。 - Anil Maurya

6

由于此运算符将比较转换为整数表达式,因此它提供了最通用的方法来基于多个列/属性升序或降序排序。

例如,如果我有一个对象数组,则可以执行以下操作:

# `sort!` modifies array in place, avoids duplicating if it's large...

# Sort by zip code, ascending
my_objects.sort! { |a, b| a.zip <=> b.zip }

# Sort by zip code, descending
my_objects.sort! { |a, b| b.zip <=> a.zip }
# ...same as...
my_objects.sort! { |a, b| -1 * (a.zip <=> b.zip) }

# Sort by last name, then first
my_objects.sort! { |a, b| 2 * (a.last <=> b.last) + (a.first <=> b.first) }

# Sort by zip, then age descending, then last name, then first
# [Notice powers of 2 make it work for > 2 columns.]
my_objects.sort! do |a, b|
      8 * (a.zip   <=> b.zip) +
     -4 * (a.age   <=> b.age) +
      2 * (a.last  <=> b.last) +
          (a.first <=> b.first)
end

这个基本模式可以推广到按任意列排序,在每个列上进行升序/降序的任意排列。

不错的例子,只是最后一个例子的效果与预期不符。因素应该按降序为2的幂,即8、-4、2、1。你写的方式(使用因素4、-3、2、1),例如“年龄+姓氏”比“邮编”更重要... - Elmar Zander
我认为你所理解的那些数字并不是你想象中的意思。每个因子都会乘以符号,其结果将为-1、0或1。2的幂在这里并不重要。-3 * (a.age <=> b.age)与3 * (b.age <=> a.age)完全相同。结果的符号决定了它是升序还是降序。 - lilole
不,这非常重要。zip的因子必须大于所有其他因子的(绝对值)之和,而age的因子必须大于last和first的因子之和(绝对值),依此类推。最小的满足条件的数字序列是2的幂序列... 顺便说一句,如果你仔细阅读我的评论,就会看到我包括了减号... - Elmar Zander
2
好的,也许我可以详细解释一下:使用因子 (4,-3,2,1) 和太空船操作的结果 (1,1,-1,-1),加权总和为-2,但它需要是正数!否则,较大的 zip 将出现在较小的 zip 之前。这种情况不会发生在因子 (8,-4,2,1) 中。 - Elmar Zander
1
啊,我现在明白了,如果要按照超过两列进行排序,则需要使用2的幂。感谢您帮助纠正这个问题。如果您的三列或更多列排序出错了,对不起世界。 - lilole

1

<=>是什么(“太空船”运算符)

根据引入该运算符的RFC, $a <=> $b

 -  0 if $a == $b
 - -1 if $a < $b
 -  1 if $a > $b

 - Return 0 if values on either side are equal
 - Return 1 if value on the left is greater
 - Return -1 if the value on the right is greater

例子:

//Comparing Integers

echo 1 <=> 1; //ouputs 0
echo 3 <=> 4; //outputs -1
echo 4 <=> 3; //outputs 1

//String Comparison

echo "x" <=> "x"; // 0
echo "x" <=> "y"; //-1
echo "y" <=> "x"; //1

更多:
// Integers
echo 1 <=> 1; // 0
echo 1 <=> 2; // -1
echo 2 <=> 1; // 1

// Floats
echo 1.5 <=> 1.5; // 0
echo 1.5 <=> 2.5; // -1
echo 2.5 <=> 1.5; // 1

// Strings
echo "a" <=> "a"; // 0
echo "a" <=> "b"; // -1
echo "b" <=> "a"; // 1

echo "a" <=> "aa"; // -1
echo "zz" <=> "aa"; // 1

// Arrays
echo [] <=> []; // 0
echo [1, 2, 3] <=> [1, 2, 3]; // 0
echo [1, 2, 3] <=> []; // 1
echo [1, 2, 3] <=> [1, 2, 1]; // 1
echo [1, 2, 3] <=> [1, 2, 4]; // -1

// Objects
$a = (object) ["a" => "b"]; 
$b = (object) ["a" => "b"]; 
echo $a <=> $b; // 0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接