Sorting and comparison
The spaceship operator
Ruby compares with <=>, which answers -1, 0 or 1. Everything that sorts uses it, and a class that defines it can be sorted, compared and put in a range.
sort calls it on pairs; sort_by runs the block once per item and sorts the keys, which is faster and easier to read when the key is not the value itself:
ruby -e 'p ["b", "A", "c"].sort_by(&:downcase)'
["A", "b", "c"]
Sort and sort_by
A plain sort on strings puts capitals before lowercase, because it compares character codes. A sort_by(&:downcase) gives the order a reader expects and keeps the original casing in the result.
ruby -e 'p ["b", "A", "c"].sort; p ["b", "A", "c"].sort_by(&:downcase)'
["A", "b", "c"]
["A", "b", "c"]
Here both lines agree; with an accented or a longer list they do not. Sorting by a key from a hash is the common case: rows.sort_by { |row| row[:name].downcase }.
Sorting text and numbers
A number and its text form sort differently. A list of strings that look like numbers sorts as text, which puts ten before two:
ruby -e 'p [10, 9, 2].sort; p %w[10 9 2].sort'
[2, 9, 10]
["10", "2", "9"]
This is not a Ruby quirk; any language that sorts strings does the same. Convert before sorting when the values are numbers: list.sort_by(&:to_i).
Comparable on your own class
Include Comparable and define <=>, and the class gains <, >, between? and clamp:
Box = Struct.new(:size) do
include Comparable
def <=>(other)
size <=> other.size
end
end
puts [Box.new(2), Box.new(1)].min.size
ruby -e 'Box = Struct.new(:size) { include Comparable; def <=>(o) = size <=> o.size }; puts [Box.new(2), Box.new(1)].min.size'
1
The comparison delegates to the value that decides the order, which keeps the rule in one place. A class with several attributes compares an array of them: [size, name] <=> [other.size, other.name].
What to watch for
sort is stable in Ruby only as a side effect of
the algorithm, not as a promise. When ties must keep their input order,
include the position in the key:
sort_by.with_index { |row, i| [row[:v], i] }.
sort without a block raises
ArgumentError as soon as two items cannot be
compared, for example a string and a number in one list. The message
names the pair, which is usually enough to find the bad row.
Next: Sets and set operations
Article author: Arthur Isaev