The <=>
method should return -1
, 0
or 1
for "less than", "equal to", and "greater than", respectively. For some types of sortable objects, it is normal to base the sort order on multiple properties. The following works, but I think it looks clumsy:
class LeagueStats
attr_accessor :points, :goal_diff
def initialize pts, gd
@points = pts
@goal_diff = gd
end
def <=> other
compare_pts = points <=> other.points
return compare_pts unless compare_pts == 0
goal_diff <=> other.goal_diff
end
end
Trying it:
[
LeagueStats.new( 10, 7 ),
LeagueStats.new( 10, 5 ),
LeagueStats.new( 9, 6 )
].sort
# => [
# #<LS @points=9, @goal_diff=6>,
# #<LS @points=10, @goal_diff=5>,
# #<LS @points=10, @goal_diff=7>
# ]
Perl treats 0
as a false value, which allows complex comparisons with different syntax:
{
return ( $self->points <=> $other->points ) ||
( $self->goal_diff <=> $other->goal_diff );
}
I find the fall-through on 0
via the ||
operator simple to read and elegant. One thing I like about using ||
is the short-circuiting of calculations once the comparison has a value.
I cannot find anything similar in Ruby. Are there any nicer ways to build the same complex of comparisons (or anything else picking the first non-zero item), ideally without needing to calculate all values in advance?
In addition to sawa's answer, the result can also should be evaluated in-place, in order to return -1, 0 or +1:
class LeagueStats
def <=> other
[points, goal_diff] <=> [other.points, other.goal_diff]
end
end
This works because of Array#<=>
:
Arrays are compared in an “element-wise” manner; the first two elements that are not equal will determine the return value for the whole comparison.
After implementing <=>
, you can include Comparable
and get <
, <=
, ==
, >=
, >
and between?
for free.
You can use Enumerable#sort_by
instead of sort
:
[LeagueStats.new(10,7),LeagueStats.new(10,5),LeagueStats.new(9,6)].sort_by { |ls|
[ls.points, ls.goal_diff]
}
# => [#<LeagueStats:0x00000001a806c8 @points=9, @goal_diff=6>,
# #<LeagueStats:0x00000001a806f0 @points=10, @goal_diff=5>,
# #<LeagueStats:0x00000001a80718 @points=10, @goal_diff=7>]
class LeagueStats
...
def to_a
[points, goal_diff]
end
def <=> other
to_a <=> other.to_a
end
end
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With