ssks
ssks

Reputation: 81

Comparison of object with fixnum

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

Answers (3)

Stefan
Stefan

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

Uri Agassi
Uri Agassi

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

sawa
sawa

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

Related Questions