The spaceship operator <=> in Ruby
Tags: beginners, codenewbie, ruby
Published: 2021-08-11 20:41:02

If you're a Ruby noob like me, you probably saw the "spaceship operator" and thought of Space Invader or Darth Vader's infamous TIE fighter.  I am currently on day 2 of learning [Ruby](https://ruby-doc.org/) going into day 3 and I had trouble understanding what this interesting looking operator does and what you could use it for.  Hopefully by the end of this post, you and I can both take away some practical use cases for this method.

##Ruby Comparison Operators
Before we start experimenting with the <=> operator, let's take a look at more common comparison operators.
```rb
 1 > 0     #=> true     (Greater than)
 0 < 1     #=> true     (Less than)
 1 >= 0    #=> true     (Greater or equal than)
 0 <= 1    #=> true     (Less or equal than)
 1 == 0    #=> false    (Equals)
 1 != 0    #=> true     (Not equals)
```
Pretty straightforward, they all __return a boolean value__.  An important takeaway from one of my readings was to remember that all Ruby operators are actually _methods_.  This is because the behavior (return value) will depend on the type of __class__ (object) it is called on.

##<=> Operator
It's technical name is the __combined comparison operator__.  The major difference compared to the other comparison operators is that it __returns 3 different values__: `(-1), (0), or (1)`. 
```rb
1 <=> 1     #=> 0
0 <=> 1     #=> -1
1 <=> 0     #=> 1
```
For `Integers`: (0) if right side = left, (-1) if less than, and (1) if greater than.
```rb
"string" <=> "string"     #=> 0     (contents are the same)
"string" <=> "stringg"    #=> -1    (length of string)
"string1" <=> "string2"   #=> -1    (string integer)
"STRING" <=> "string"     #=> -1    (capital)
```
For `Strings` the behavior is not as straight forward.  It looks like uppercase letters are given a lower value than lowercase letters.  Why?
{% link https://dev.to/sylviapap/the-ruby-enumerable-operator-and-max-vs-maxby-ac6 %}
Another great post on this topic mentions how characters in strings are compared with binary values under the hood, which helps to better understand the above return values.
```rb
fighter1 = {type: "TIE Fighter", owner: "Darth Vader", affiliation: "Galactic Empire", id: 1}
fighter2 = {type: "X-wing", owner: "Luke Skywalker", affiliation: "Rebel Alliance", id: 2}

fighter1 <=> fighter1     #=> 0
fighter2 <=> fighter1     #=> nil
fighter1 <=> fighter2     #=> nil
```
For `Hashes` it's even less clear...
```rb
fighter1 = {type: "TIE Fighter", owner: "Darth Vader", affiliation: "Galactic Empire", id: 1}
fighter2 = {type: "TIE Fighter", owner: "Darth Vader", affiliation: "Galactic Empire", id: 1}

fighter1 <=> fighter1     #=> 0
fighter2 <=> fighter1     #=> 0
fighter1 <=> fighter2     #=> 0
```
Here the contents are exactly the same with expected results.
```rb
fighter1 = {type: "TIE Fighter", owner: "Darth Vader", affiliation: "Galactic Empire", id: 1}
fighter2 = {type: "X-wing", owner: "Luke Skywalker", affiliation: "Rebel Alliance", id: 2}

fighter1[:owner] <=> fighter1[:owner]     #=> 0
fighter2[:owner] <=> fighter1[:owner]     #=> 1
fighter1[:owner] <=> fighter2[:owner]     #=> -1
```
Here it looks like the string length comes in to play after changing fighter2 back to normal. 
```rb
fighter1 = {type: "TIE Fighter", owner: "Darth Vader", affiliation: "Galactic Empire", id: 1}
fighter2 = {type: "X-wing", owner: "Luke Skywalker", affiliation: "Rebel Alliance", id: 2}

fighter1[:id] <=> fighter1[:id]     #=> 0
fighter2[:id] <=> fighter1[:id]     #=> 1
fighter1[:id] <=> fighter2[:id]     #=> -1
```
Even though these examples do not showcase every possible scenario for these data types, we now know that the behavior of the <=> operator for `Arrays` and `Hashes` is a little unpredictable depending on their respective contents.  Even [Ruby's documentation](https://ruby-doc.org/core-2.7.3/Enumerable.html#method-i-sort) mentions how unpredictable the return value could be based on the objects being compared. 
 That's still not very helpful for how we could use this in a practical way.
![](https://media.giphy.com/media/l1KsOGE23suZQeCfm/giphy.gif)
##Using <=> with Ruby's `sort` method
```rb
star_fighters = [
  {type: "TIE Fighter", owner: "Darth Vader", affiliation: "Galactic Empire", id: 1}, 
  {type: "X-wing", owner: "Luke Skywalker", affiliation: "Rebel Alliance", id: 2},
  {type: "Jedi Starfighter", owner: "Obi-Wan Kenobi", affiliation: "The Republic", id: 3}

]

sorted_star_fighters = star_fighters.sort do |fighter1, fighter2|
  fighter1[:id] <=> fighter2[:id]
end
puts sorted_star_fighters
#=> {:type=>"TIE Fighter", :owner=>"Darth Vader", :affiliation=>"Galactic Empire", :id=>1}, {:type=>"X-wing", :owner=>"Luke Skywalker", :affiliation=>"Rebel Alliance", :id=>2}, {:type=>"Jedi Starfighter", :owner=>"Obi-Wan Kenobi", :affiliation=>"The Republic", :id=>3}
```
In this scenario, our hashes are being ordered from lowest to highest because based on how our id's are set up, fighter2's id will always be greater than fighter1's, which will return -1.
```rb
star_fighters = [
  {type: "TIE Fighter", owner: "Darth Vader", affiliation: "Galactic Empire", id: 1}, 
  {type: "X-wing", owner: "Luke Skywalker", affiliation: "Rebel Alliance", id: 2},
  {type: "Jedi Starfighter", owner: "Obi-Wan Kenobi", affiliation: "The Republic", id: 3}

]

sorted_star_fighters = star_fighters.sort do |fighter1, fighter2|
  fighter2[:id] <=> fighter1[:id]
end
puts sorted_star_fighters
#=> {:type=>"Jedi Starfighter", :owner=>"Obi-Wan Kenobi", :affiliation=>"The Republic", :id=>3}, {:type=>"X-wing", :owner=>"Luke Skywalker", :affiliation=>"Rebel Alliance", :id=>2}, {:type=>"TIE Fighter", :owner=>"Darth Vader", :affiliation=>"Galactic Empire", :id=>1}
```
In this scenario, our hashes are being ordered from highest to lowest because in this case, fighter2's id will always be greater than fighter1's id, which will return 1.
##Takeaways
1. Return values are integers: `(-1), (0), or (1)`
2. Return values can be unpredictable with `Strings` & `Hashes` depending on their contents which makes `Integers` a more attractive option to work with.
3. The <=> operator can come in handy when using `sort` to order your data from ascending or descending order in a cleaner way instead of doing something like this:
```rb
star_fighters = [
  {type: "TIE Fighter", owner: "Darth Vader", affiliation: "Galactic Empire", id: 1}, 
  {type: "X-wing", owner: "Luke Skywalker", affiliation: "Rebel Alliance", id: 2},
  {type: "Jedi Starfighter", owner: "Obi-Wan Kenobi", affiliation: "The Republic", id: 3}

]

sorted_star_fighters = star_fighters.sort do |fighter1, fighter2|
  if fighter1[:id] == fighter2[:id]
    0
  elsif fighter1[:id] < fighter2[:id]
    -1
  elsif fighter1[:id] > fighter2[:id]
    1
  end
end
puts sorted_star_fighters
#=> {:type=>"TIE Fighter", :owner=>"Darth Vader", :affiliation=>"Galactic Empire", :id=>1}, {:type=>"X-wing", :owner=>"Luke Skywalker", :affiliation=>"Rebel Alliance", :id=>2}, {:type=>"Jedi Starfighter", :owner=>"Obi-Wan Kenobi", :affiliation=>"The Republic", :id=>3}
```

##Conclusion
Based on my short experience with the <=> operator, it seems to be most useful in tandem with the `sort` enumerator.  I would love to hear if anyone else knows any other use cases in the comments below.  Hopefully this post gave you a slightly better understanding of the <=> operator.  I am definitely going to continue to experiment with it.  Other languages that use the <=> operator include: Perl, Apache, Groovy, PHP, Eclipse, Ceylon, and C++.  Having a good grasp on how and when to use conditional operators to compare values will allow you to control the flow of your data like a jedi controlling the flow of The Force.
![](https://media.giphy.com/media/3ohuPp7uWLgTdU83iU/giphy.gif)

##Resources
[Ruby equality](https://www.rubyguides.com/2017/03/ruby-equality/)
[Ruby operators](https://www.rubyguides.com/2018/07/ruby-operators/)
[Ruby sort enumerator](https://ruby-doc.org/core-2.7.3/Enumerable.html#method-i-sort)
[HTML Encoding (Character Sets)](https://www.w3schools.com/html/html_charset.asp)
https://www.youtube.com/watch?v=tpsdxtf01po
https://www.youtube.com/watch?v=f4NItw7r33E