There are plenty of cool things about Ruby. And the so called
Spaceship Operator (because
<=> looks like a spaceship, obvs)
is definitely one of the coolest.
But what does it do?
<=> operator returns 1, 0, or -1 depending on the comparison.
1if the value on the left is greater than the value on the right
0if the two values are considered equal
-1if the value on the right is considered greater
So far, so good.
When dealing with Numeric object, this is pretty obvious.
100 <=> 99 # returns 1 99 <=> 99 # returns 0 99 <=> 100 # returns -1
It’s less obvious when dealing with Strings.
"boys" <=> "girls" # returns -1 ("girls" wins)
Don’t be fooled into thinking girls are better than boys because there’s more of them. (Characters, I mean.)
"big boys" <=> "girls" # returns -1 ("girls" wins)
Ruby is actually comparing them on an alphabetical basis. The
g being greater
What’s more, case matters.
b is greater than
A..Za..z is the logical
way to approach this).
"boys" <=> "Girls" # returns 1 ("boys" wins)
And you can put all this together by using
Array#sort because under the hood
.sort is using the spaceship.
["boys", "girls", "Girls"].sort # => ["Girls", "boys", "girls"]
The spaceship operator isn’t limited to Ruby’s standard library.
Take this tiny
class Song def initialize(name) @name = name end end
And since we’ve got Girls and Boys on the brain, let’s go back to the great Oasis vs Blur chart battle of 1995.
Deciding which song was better is now trivial using Ruby’s sort, right?
[Song.new('Country House'), Song.new('Roll With It')].sort
ArgumentError (comparison of Song with Song failed)
The reason for this
ArgumentError is that we haven’t told Ruby how to
compare these two songs. Doing so requires us to implement the
So let’s implement the spaceship operator in our class
class Song attr_reader :name, :sales def initialize(name, sales) @name = name @sales = sales end def <=> (song) sales <=> song.sales end end
run the comparison again with same sales figures
[ Song.new('Country House', 274_000), Song.new('Roll With It', 214_000) ].sort.map(&:name)
and see the correct answer:
["Roll With It", "Country House"]
Of course, this wasn’t how the charts looked in 1995 because Ruby’s number sort goes from low to high.
However, if you’re desperate to see Blur come out victorious again, then you
can now include the
Comparable module in your class (which uses
<=> to give
you access to your favourite comparison operators such as
So now your class looks like this:
class Song include Comparable attr_reader :name, :sales def initialize(name, sales) @name = name @sales = sales end def <=> (song) sales <=> song.sales end end
You’re desperate for proof that
Song.new('Country House', 274_000) > Song.new('Roll With It', 214_000)
And you get it: