Ruby Spaceship <=> Operator

Adhering to the law of trichotomy, the <=> operator (sometimes called the “Spaceship Operator”) works by comparing two elements and returning a -1 , 0 , or 1 . While the original mathematical criteria applies to only real numbers, many programming languages implement the law of trichotomy as a general comparison between equivalent types.

Basics

At first glance, the return value of the <=> operator can be a bit confusing. A simple way to remember the significance of these return values is to break an expression down from left to right:

1. < –> -1

if a < b , then -1 is returned

2. = –> 0

if a = b , then 0 is returned

3. > –> 1

if a > b , then 1 is returned

Example:

> a = 3 > b = 5 > a <=> b # => -1 > a = 3 > b = 3 > a <=> b # => 0 > a = 10 > b = 3 > a <=> b # => 1

Sorting

The <=> operator can be used alone for comparison or its contract honored within a block following the sort method.

By default, the <=> operator behaves as described above:

list = [ 8 , 3 , 1 , 4 , 0 , 3 ] list . sort { | a , b | a <=> b } # => [0, 1, 3, 3, 4, 8]

Following this pattern, it is easy to sort a list in reverse by swapping operand positions:

list = [ 8 , 3 , 1 , 4 , 0 , 3 ] list . sort { | a , b | b <=> a } # => [8, 4, 3, 3, 1, 0]

The sort method is extendable beyond explicit use of the <=> operator. A block passed to sort must only return either -1 , 0 , or 1 for sorting to work effectively.

If the same list were to be ordered in odds then evens:

list = [ 8 , 3 , 1 , 4 , 0 , 3 ] list . sort { | a , _ | a . odd? ? - 1 : 1 } # => [3, 3, 1, 4, 8, 0]

Also, since -1 , 0 , and 1 are simple integers, creating compound <=> blocks is possible. If the same list were to be sorted odds then evens, with all odd and even numbers sorted in ascending order, it might look like this:

list = [ 8 , 3 , 1 , 4 , 0 , 3 ] list . sort do | a , b | if a . odd? if b . odd? # both are odd, default <=> behaviour is used a <=> b else - 1 # a < b end else # a is even if b . even? # both are even, default <=> behaviour is used a <=> b else 1 # a > b end end end # => [1, 3, 3, 0, 4, 8]

Far from elegant, this code effectively sorts the array of numbers first by odds to evens and then in ascending order.

Custom Methods

While thought of and generally referred to as an operator, <=> is actually a method. Defined originally on Object , any object has the opportunity to redefine this method to achieve custom sort behaviour.

An example class, Node , has a single attribute value :

class Node attr_accessor :value end

By default, sorting an array of Node objects does not produce the desired result:

node1 = Node . new node1 . value = 20 node2 = Node . new node2 . value = 10 node3 = Node . new node3 . value = 30 list = [ node1 , node2 , node3 ] list . sort # => ArgumentError: comparison of Node with Node failed

However, with a custom <=> method:

class Node attr_accessor :value def < => ( other_node ) self . value <=> other_node . value end end list = [ node1 , node2 , node3 ] list . sort # => [#<Node: @value=10>, #<Node: @value=20>, #<Node: @value=30>]

Boom! Custom sorting in the mix!

Admittedly, this same behaviour is possible with the built in sort_by method:

list = [ node1 , node2 , node3 ] list . sort_by ( & :value ) # => [#<Node: @value=10>, #<Node: @value=20>, #<Node: @value=30>]

But, what happens when the Node class wants to expose a sorting mechanism while removing its value method? In that case, the sort_by method would no longer be sufficient.

Defining a custom <=> method also allows objects to include the Comparable mixin effectively. The Comparable mixin provides the conventional comparison operators ( < , > , <= , etc.).

class Node include Comparable attr_accessor :value def < => ( other_node ) self . value <=> other_node . value end end node1 = Node . new node1 . value = 200 node2 = Node . new node2 . value = 300 node1 > node2 # => false node1 < node2 # => true node3 = Node . new node3 . value = 250 node3 . between? ( node1 , node2 ) # => true