What is the Ruby <=> (spaceship) operator?
Asked Answered
T

6

305

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

Testa answered 6/5, 2009 at 1:27 Comment(5)
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] ?Mittel
@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].Thermograph
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.Klaxon
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.Macintosh
@Thermograph 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).Homogenesis
K
439

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.

Kyrakyriako answered 6/5, 2009 at 1:30 Comment(5)
Exactly. I think of it as a very elegant version of Java's Comparable.Thelmathem
analog in c# is IComparable.CompareToShevat
Actually I think any negative or positive value can be returned. 0 still means equality.Mervinmerwin
Note that if the two objects compared are not comparable, you get a nilKathlenekathlin
@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 -- open-std.org/JTC1/SC22/WG21/docs/papers/2017/p0515r0.pdf ("Consistent comparison", by Herb Sutter).Varipapa
M
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>]
Mcgarry answered 11/1, 2013 at 16:24 Comment(0)
D
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.

Dodona answered 6/5, 2009 at 1:31 Comment(0)
S
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.

Severin answered 11/1, 2013 at 14:2 Comment(2)
does it just compare the first left element of each array or continue to compare other elements as well? good explanationLonilonier
@KickButtowski it continue to compare other elements unless it find an inequal number.Severin
M
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.

Macintosh answered 11/4, 2017 at 23:22 Comment(5)
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"...Sonata
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.Macintosh
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...Sonata
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).Sonata
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.Macintosh
R
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
Roomer answered 27/6, 2017 at 8:29 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.