6

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?

Richard Stokes
  • 3,532
  • 7
  • 41
  • 57
  • 1
    Have 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 Answers3

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
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
  • Thanks, this was the kind of thing I was looking for – Richard Stokes Nov 14 '11 at 15:32
  • 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
Community
  • 1
  • 1
notapatch
  • 6,569
  • 6
  • 41
  • 45