Is there an easy way (i.e. using the spaceship operator) to define comparison in Ruby based on two different attributes? I.e. If I have a class that contains two attributes, attr1 and attr2, is there a Rubyesque way of comparing two instances of this class on attr1, and if they're equal then compare them on attr2?
Asked
Active
Viewed 3,772 times
6
-
1Have you looked at the docs: http://www.ruby-doc.org/core-1.9.3/Comparable.html? – Benoit Garret Nov 14 '11 at 15:17
-
it's easy to overload the spaceship operator (just add `def <=>(other) ... end`) but you have to figure out a schema how to compare these two instances. Let's say you want to compare instance a with instance b (based on attr1 and attr2): 1. when is a > b ? 2. when is a == b ? 3. when is a < b? – sled Nov 14 '11 at 15:21
3 Answers
16
This is an easily extendible (to more attributes) way:
def <=>(other)
[self.attr1, self.attr2] <=> [other.attr1, other.attr2]
end

steenslag
- 79,051
- 16
- 138
- 171
-
This dosen't work for me. My solution is exactly the same as Gavin Miller and it works like a charm. – udit mittal Nov 10 '14 at 08:37
7
The whole point of the comparable mixin is to provide a definition for the spaceship (comparison) operator. So if you want to do a comparison across two attributes, then do it. Here's an overly verbose example:
def <=>(obj)
comparison = self.attr1 <=> obj.attr1
if comparison == 0
return self.attr2 <=> obj.attr2
else
return comparison
end
end
Obviously the above assumes attr1
, and attr2
both have definitions for the spaceship operator. As well you'll need to determine what constitutes greater than, and less than, which is likely a bit difficult across two attributes. Which suggests that comparable may not be the proper code for your scenario.
A more succinct and idiomatic way of writing this would be:
def <=>(obj)
self.attr1 <=> obj.attr1 == 0 ? self.attr2 <=> obj.attr2 : self.attr1 <=> obj.attr1
end

Gavin Miller
- 43,168
- 21
- 122
- 188
-
-
What would be a more concise formulation? One using the ternary operator? – N.N. Jan 15 '13 at 09:53
-
@N.N. yes. `comparison == 0 ? self.attr2 <=> obj.attr2 : comparison` which could be reduced further into a single statement by inlining the `comparison` variable. – Gavin Miller Jan 15 '13 at 14:29
-
If that is a more idiomatic example of such a function your answer would be better if it included it. – N.N. Jan 15 '13 at 17:18
-
@N.N. agreed. Given the nature of the question, the questioner would likely have not understood the idiomatic answer. However, it's worth adding it in. Thanks for the suggestion! :D – Gavin Miller Jan 15 '13 at 17:56
2
Spaceship with 'not comparable'
Spaceship requires:
a <=> b :=
if a < b then return -1
if a = b then return 0
if a > b then return 1
if a and b are not comparable then return nil <= I wanted this
Steenslag's solution wasn't giving me the nil when 'not comparable'.
Impementation
Added extra line for coping with 'not comparable':
def <=> other
return nil unless other.is_a?(self.class)
[self.attr1, self.attr2] <=> [other.attr1, other.attr2]
end