Spaceship Operator <=> em Ruby

Artigos - 30/Jul/2020 - por André Kanamura

Em programação, além dos operadores de comparação mais comuns, como <, > ou ==, em algumas linguagens podemos encontrar o Spaceship Operator <=>, que faz uma "comparação de três vias": são realizadas três comparações entre dois valores numa única operação utilizando os operadores <, == e >. Mas o que isso significa? Em resumo, dado a <=> b os valores são comparados e um resultado diferente será retornado dependendo da relação entre eles:

retorna -1 se a < b
retorna 0 se a == b
retorna 1 se a > b

Vamos ver alguns exemplos simples:

1 <=> 2
# => -1
# já que 1 < 2, o retorno é -1

2 <=> 1
# => 1
# como 2 é maior que 1, o retorno é 1

1 <=> 1
# => 0 
# quando os valores comparados são iguais, o retorno é 0

Esse operador nos dá resultados interessantes quando usados de outras maneiras. Vamos, por exemplo, usar o <=> junto ao método group_by:

Array(1..10).group_by { |n| n <=> 5 }
# => {-1=>[1, 2, 3, 4], 0=>[5], 1=>[6, 7, 8, 9, 10]}

Note como o retorno foi uma Hash que separa os números em três grupos de acordo com sua relação com o valor de referência, nesse caso o 5.

Talvez o mais interessante seja a forma como o Spaceship Operator interage com o método sort:

["coelho", "gato", "cavalo", "zebra", "ema"].sort { |a, b| a <=> b }
# => ["cavalo", "coelho", "ema", "gato", "zebra"]

["coelho", "gato", "cavalo", "zebra", "ema"].sort { |a, b| b <=> a }
# => ["zebra", "gato", "ema", "coelho", "cavalo"]

O primeiro exemplo, usando a <=> b, ordena as palavras em ordem alfabética e o segundo, com b <=> a, na ordem inversa. Isso acontece porque alterando a ordem de a e b, os retornos são diametralmente opostos. Essa estrutura pode ser adaptada para todo tipo de ordenação personalizada, atentando somente ao fato de que em comparações que resultam em 0, a ordem dos elementos pode ser imprevisível. No exemplo abaixo, por exemplo, as palavras "cavalo" e "coelho" permanecem na mesma ordem em que se encontram no array original:

["coelho", "gato", "cavalo", "zebra", "ema"].sort { |a, b| a.length <=> b.length }
# => ["ema", "gato", "zebra", "coelho", "cavalo"]

["coelho", "gato", "cavalo", "zebra", "ema"].sort { |a, b| b.length <=> a.length }
# => ["coelho", "cavalo", "zebra", "gato", "ema"]

Em alguns casos, como no exemplo acima, será mais fácil utilizar o método sort_by e passar a ele apenas um objeto com o critério de ordenação desejado. Por exemplo:

["coelho", "gato", "cavalo", "zebra", "ema"].sort_by { |a| a.length }
# => ["ema", "gato", "zebra", "coelho", "cavalo"]

Além disso, se o seu objetivo for separar elementos de um Array ou String de acordo com um determinado critério, você pode tentar usar o método partition (Enumerable, String).

Referências

Foto de perfil do autor
André Kanamura

Dev na Campus Code