What is the Ruby <=> (spaceship) operator?
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,3,2] <=> [2,2,2]
Ruby will start comparing each element of both array from left hand side.
1
for left array is smaller than2
of right array. Hence left array is smaller than right array. Output will be-1
.[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.
Related videos on Youtube
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&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, 2022Comments
-
Justin Ethier almost 2 years
What is the Ruby
<=>
(spaceship) operator? Is the operator implemented by any other languages?-
SF. about 14 yearsNow 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 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 about 8 yearsNote 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 about 7 yearsWhen comparing arrays like
[1,nil] <=> [1,3]
you get anil
because of the consistency of the algorithm, comparing each element in turn until the<=>
result is NOT0
. There's no way for Ruby to declare less-than or greater-than in this example, since a comparison simply cannot be made. Thenil
should be treated as "not equal". If you know something about the data, and e.g. want to treatnil
as0
, Ruby makes that easy.
-
-
Mike Reedell almost 15 yearsExactly. I think of it as a very elegant version of Java's Comparable.
-
Sergey Mirvoda over 14 yearsanalog in c# is IComparable.CompareTo
-
superluminary over 10 yearsActually I think any negative or positive value can be returned. 0 still means equality.
-
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 almost 10 yearsNote that if the two objects compared are not comparable, you get a nil
-
Kick Buttowski about 8 yearsdoes it just compare the first left element of each array or continue to compare other elements as well? good explanation
-
Anil Maurya about 8 years@KickButtowski it continue to compare other elements unless it find an inequal number.
-
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 over 4 yearsNice 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 over 4 yearsI 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 over 4 yearsNope, 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 over 4 yearsOk, 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 over 4 yearsAh 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.