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

120,307

Solution 1

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.

Solution 2

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

Solution 3

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.

Solution 4

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.

Solution 5

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.

Share:
120,307

Related videos on Youtube

Justin Ethier
Author by

Justin Ethier

Software Engineer living in the Baltimore area. Open Source Projects - A brand-new compiler that allows practical application development using R7RS Scheme. - A practical implementation of the Scheme programming language for the Haskell Platform. stack-watch - A unix command-line utility to automatically monitor Q&amp;A activity on Stack Exchange. node-kdtree - A node.js add-on for performing efficient Nearest Neighbor searches using libkdtree. Minor contributions to many projects including jsgauge, jqGrid, Highcharts, Haskell SELinux bindings, chibi-scheme, jQuery UI Spinner, jClock, and the jQuery Validation Plugin. Featured solutions to Ruby Quiz Mexican Blanket Vehicle Counters If you develop a program for a long period of time by only adding features but never reorganizing it to reflect your understanding of those features, then eventually that program simply does not contain any understanding and all efforts to work on it take longer and longer. There is no right answer ... and always a better way. Show and discuss your code, without emotional attachment. You are not your code. Focused, hard work is the real key to success. Keep your eyes on the goal, and just keep taking the next step towards completing it. The game isn't really about big edges and firework displays; it's about subtle advantages and what happens in the long run. It is amazing what you can accomplish if you do not care who gets credit. Maybe the best programmers aren’t those who spectacularly solve crazy problems, but those who don’t create them, which is much more silent.

Updated on May 11, 2022

Comments

  • Justin Ethier
    Justin Ethier almost 2 years

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

    • SF.
      SF. about 14 years
      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] ?
    • liori
      liori about 14 years
      @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].
    • cliffordheath
      cliffordheath about 8 years
      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.
    • lilole
      lilole about 7 years
      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.
  • Mike Reedell
    Mike Reedell almost 15 years
    Exactly. I think of it as a very elegant version of Java's Comparable.
  • Sergey Mirvoda
    Sergey Mirvoda over 14 years
    analog in c# is IComparable.CompareTo
  • superluminary
    superluminary over 10 years
    Actually I think any negative or positive value can be returned. 0 still means equality.
  • TonyArra
    TonyArra over 10 years
    @superluminary Unlike C's strcmp function, x <=> y is designed specifically to only return -1, 0, 1, or nil if x and y are not comparable (in Ruby and any other languages that use it AFAIK). This makes it easy to overload the operator, such as for Ruby's Comparable mixin. In Perl, where the operator most likely originated, it was used mainly to simplify the "sort BLOCK LIST" syntax. The BLOCK is a subroutine that can return any positive number, negative number, or 0 depending on how the list items should be sorted. Spaceship operator is convenient to use in the block.
  • gamov
    gamov almost 10 years
    Note that if the two objects compared are not comparable, you get a nil
  • Kick Buttowski
    Kick Buttowski about 8 years
    does it just compare the first left element of each array or continue to compare other elements as well? good explanation
  • Anil Maurya
    Anil Maurya about 8 years
    @KickButtowski it continue to compare other elements unless it find an inequal number.
  • lindes
    lindes almost 6 years
    @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).
  • Elmar Zander
    Elmar Zander over 4 years
    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"...
  • lilole
    lilole over 4 years
    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.
  • Elmar Zander
    Elmar Zander over 4 years
    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
    Elmar Zander over 4 years
    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).
  • lilole
    lilole over 4 years
    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.