301

What is the Ruby <=> (spaceship) operator? Is the operator implemented by any other languages?

Andrew Marshall
  • 95,083
  • 20
  • 220
  • 214
Justin Ethier
  • 131,333
  • 52
  • 229
  • 284
  • 1
    Now what about comparing arrays? It said in the book "compares element by element, returns 0 if equal, -1 if lesser, 1 if greater, but what about `[1,3,2] <=> [2,2,2]` ? – SF. Apr 16 '10 at 10:22
  • 3
    @SF, when people compare arrays, they usually mean to compare lexicographically (like in a dictionary, i.e. [1,3,2] < [2,2,2] because first elements are different). Rarely (f.e. in Matlab) array comparision returns an array of results per element; in this case: [-1, 1, 0]. – liori Apr 16 '10 at 10:39
  • Note that Arrays which contain nil elements are comparable if the elements before any nil are different, and not comparable if a nil must be compared with non-nil. I.e. [1, nil] <=> [2, 3] => -1, but [1, nil] <=> [1, 3] => nil. This sucks, basically. – cliffordheath Mar 29 '16 at 01:26
  • When comparing arrays like `[1,nil] <=> [1,3]` you get a `nil` because of the consistency of the algorithm, comparing each element in turn until the `<=>` result is NOT `0`. There's no way for Ruby to declare less-than or greater-than in this example, since a comparison simply cannot be made. The `nil` should be treated as "not equal". If you know something about the data, and e.g. want to treat `nil` as `0`, Ruby makes that easy. – lilole Apr 11 '17 at 23:33
  • 1
    @liori you should use e.g. ("exempli gratia," which is for example in English) instead of f.e. I believe it's clearer and more widely accepted (across other latin languages for instance). – Aiden Cullo Jul 24 '23 at 15:51

6 Answers6

434

The spaceship operator will return 1, 0, or −1 depending on the value of the left argument relative to the right argument.

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

It's commonly used for sorting data.

It's also known as the Three-Way Comparison Operator. Perl was likely the first language to use it. Some other languages that support it are Apache Groovy, PHP 7+, and C++20.

TonyArra
  • 10,607
  • 1
  • 30
  • 46
  • 30
    Exactly. I think of it as a very elegant version of Java's Comparable. – Mike Reedell May 06 '09 at 12:42
  • 12
    analog in c# is IComparable.CompareTo – Sergey Mirvoda Aug 07 '09 at 06:21
  • 2
    Actually I think any negative or positive value can be returned. 0 still means equality. – superluminary Nov 28 '13 at 11:35
  • 3
    Note that if the two objects compared are not comparable, you get a nil – gamov May 27 '14 at 07:36
  • @TonyArra: looks like C++20 might at least allow implementations to use non-1-based integers, observing only sign. I don't know how likely it is that that'll actually happen, just sharing for completeness. See the note on "Efficiency" on page 12 of P0515 R0 -- http://open-std.org/JTC1/SC22/WG21/docs/papers/2017/p0515r0.pdf ("Consistent comparison", by Herb Sutter). – lindes Jul 05 '18 at 23:09
81

The spaceship method is useful when you define it in your own class and include the Comparable module. Your class then gets the >, < , >=, <=, ==, and between? methods for free.

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>]
steenslag
  • 79,051
  • 16
  • 138
  • 171
20

I will explain with simple example

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

    Ruby will start comparing each element of both array from left hand side. 1 for left array is smaller than 2 of right array. Hence left array is smaller than right array. Output will be -1.

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

    As above it will first compare first element which are equal then it will compare second element, in this case second element of left array is greater hence output is 1.

Community
  • 1
  • 1
Anil Maurya
  • 2,298
  • 1
  • 22
  • 28
20

It's a general comparison operator. It returns either a -1, 0, or +1 depending on whether its receiver is less than, equal to, or greater than its argument.

gnovice
  • 125,304
  • 15
  • 256
  • 359
6

Since this operator reduces comparisons to an integer expression, it provides the most general purpose way to sort ascending or descending based on multiple columns/attributes.

For example, if I have an array of objects I can do things like this:

# `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

This basic pattern can be generalized to sort by any number of columns, in any permutation of ascending/descending on each.

lilole
  • 322
  • 2
  • 9
  • Nice examples, just that the last one does not work as expected. The factors should be powers of two in descending order, i.e. 8, -4, 2, 1. The way you wrote it (with factors 4,-3,2,1), e.g. "age + lastname" counts more than "zip"... – Elmar Zander Sep 23 '19 at 05:51
  • I don't think those numbers mean what you think they mean. Each factor multiplies the signum, which will be -1, 0, or 1. Powers of 2 doesn't matter here. The -3 * (a.age <=> b.age) is exactly the same as 3 * (b.age <=> a.age). The sign of the result is what makes it asc or desc. – lilole Sep 24 '19 at 16:55
  • Nope, it does matter a lot. The factor for zip must be larger than the (absolute) sum of all the other factors, and the factor for age must be larger than the (absolute) sum of the factors of last and first, and so on. And the smallest sequence of numbers which fulfills that is the sequence of powers of two... And BTW if you read my comment carefully, you would have seen that I included the minus sign... – Elmar Zander Sep 25 '19 at 18:11
  • 2
    Ok, maybe I'll elaborate a bit more on that: with factors (4,-3,2,1) and results from the spaceship op (1,1,-1,-1) the weighted sum is -2, but it needs to be positive! Otherwise the larger zip will come before the smaller zip. This will not happen with factors (8,-4,2,1). – Elmar Zander Sep 25 '19 at 18:14
  • 1
    Ah I see now, if sorting on > 2 columns then the powers of 2 is required. Thanks for helping to correct this. Sorry world if your 3 or more columns sorting turned out wrong. – lilole Sep 26 '19 at 19:34
1

What is <=> ( The 'Spaceship' Operator )

According to the RFC that introduced the operator, $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

Example:

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

MORE:

// 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
RïshïKêsh Kümar
  • 4,734
  • 1
  • 24
  • 36