Ruby comes with the handy <=>
comparison operators, and native primitive types support them. I am wondering if there is an easy way to combine them to compare more complex objects, like Structs.For example, given
class Datum < Struct.new(:code, :volume); end
datum1 = Datum.new('B',10)
datum2 = Datum.new('A',10)
datum3 = Datum.new('C',11)
data = [datum1, datum2, datum3]
I would like to sort data
by volume
and then, if volume
s are equal, by code
. Like
data.sort {|a,b| (a.volume <=> b.volume) ??? (a.code <=> b.code)}
What should I put in ???
?
I am after a solution that:
<=>
For such a simple case as outlined above, you can use sort_by
:
data.sort_by {|a| [a.volume, a.code] }
#=> [
# #<struct Datum code="A", volume=10>,
# #<struct Datum code="B", volume=10>,
# #<struct Datum code="C", volume=11>
# ]
If you're only sorting by a single attribute, it gets even shorter:
data.sort_by(&:volume)
#=> [
# #<struct Datum code="B", volume=10>,
# #<struct Datum code="A", volume=10>,
# #<struct Datum code="C", volume=11>
# ]
where &:volume
uses Symbol#to_proc
and is shorthand for proc {|a| a.volume }
(similar to lambda).
If you need to make it more complex (i.e. have different left and right sides), you can expand this to a call to sort
:
data.sort {|a,b| [a.volume, a.code] <=> [b.volume, b.code] }
#=> [
# #<struct Datum code="A", volume=10>,
# #<struct Datum code="B", volume=10>,
# #<struct Datum code="C", volume=11>
# ]
This whole thing works, because the <=>
operator defined on Array
does exactly what you need, for arbitrary levels.