Reputation: 81
I want to be able to compare objects with fixnums.
class Dog
include Comparable
def <=>(other)
1
end
end
Given the class above, why does this work:
dog = Dog.new
array = [2, 1, 4, dog]
array.min
=> 1
But this does not:
dog = Dog.new
array = [dog, 2, 1, 4]
array.min
ArgumentError: comparison of Fixnum with Dog failed
from (irb):11:in `each'
from (irb):11:in `min'
from (irb):11
When the object is the first element, why can't I compare the object with the numbers? Is there any way I can solve this issue? I want the Dog object to always be the greatest value when it's compared with any other value in the array, which is why I've set it to 1 in <=> method.
Thanks!
Upvotes: 4
Views: 118
Reputation: 114158
To compare against numeric types, you can implement coerce
:
class Dog
include Comparable
def initialize(age)
@age = age
end
def <=>(other)
@age <=> other
end
def coerce(numeric)
[numeric, @age]
end
end
Dog.new(5) <=> Dog.new(5) #=> 0
Dog.new(5) <=> 1 #=> 1
1 <=> Dog.new(5) #=> -1
[1, 3, Dog.new(2)].sort
#=> 1, #<Dog @age=2>, 3]
The above sorting can be achieved without implementing <=>
and coerce
using sort_by
:
class Dog
attr_accessor :age
def initialize(age)
@age = age
end
end
[1, 3, Dog.new(2)].sort_by { |obj| obj.is_a?(Dog) ? obj.age : obj }
#=> 1, #<Dog @age=2>, 3]
There's also min_by
and max_by
.
Upvotes: 4
Reputation: 37409
In order to allow for Dog
to be compared with Fixnum
you need to implement the comparison on Fixnum
as well:
class Fixnum
alias :my_compare :'<=>'
def <=>(other)
if other.is_a? Dog
return -1
end
my_compare(other)
end
end
array.min
# => 1
array.max
# => #<Dog:0x8f712fc>
Upvotes: 3
Reputation: 168081
That is because <=>
is not a binary relation in the mathematical sense (i.e., defined on A^2 for some set A), but is a method on an object, and is not symmetric. Allowing other
in Foo#<=>(other)
to be an instance of Bar
does not mean that other
in Bar#<=>(other)
can be Foo
.
Upvotes: 0