Overriding Ruby's spaceship operator <=>
Asked Answered
R

2

5

I am trying to override Ruby's <=> (spaceship) operator to sort apples and oranges so that apples come first sorted by weight, and oranges second, sorted by sweetness. Like so:

module Fruity
  attr_accessor :weight, :sweetness

  def <=>(other)
    # use Array#<=> to compare the attributes
    [self.weight, self.sweetness] <=> [other.weight, other.sweetness]
  end
  include Comparable
end

class Apple
include Fruity

def initialize(w)
  self.weight = w
end

end

class Orange
include Fruity

def initialize(s)
  self.sweetness = s
end

end

fruits = [Apple.new(2),Orange.new(4),Apple.new(6),Orange.new(9),Apple.new(1),Orange.new(22)]

p fruits

#should work?
p fruits.sort

But this does not work, can someone tell what I am doing wrong here, or a better way to do this?

Redeploy answered 17/6, 2010 at 4:10 Comment(1)
UPDATE: It turns out I was going about this the wrong way. Hint: the answer lies here -- blog.hasmanythrough.com/2008/8/17/sorting-things-outRedeploy
L
12

Your problem is you are only initializing one of the properties on either side, the other one will still be nil. nil isn't handled in the Array#<=> method, which ends up killing the sort.

There are a few ways to handle the problem first would be something like this

[self.weight.to_i, self.sweetness.to_i] <=> [other.weight.to_i, other.sweetness.to_i]

nil.to_i gives you 0, which will let this work.

Labe answered 17/6, 2010 at 4:30 Comment(7)
thanks, it worked. They are ordered by weight then sweetness, but in descending order. Is there a 'best' way to order them ascending?Redeploy
let me clarify my above comment-- if i make the following change: [self.weight.to_i, self.sweetness.to_i] <=> [other.weight.to_i, other.sweetness.to_i] I get oranges first ascending, then apples ascending, but I am not able to get apples first ascending, then oranges ascending. I have tried permuting the arrays, and even toying with Array#reverse with no luck. Also, does it matter if the Comparable mixin is included before or after the <=> definition? I am getting similar results either way.Redeploy
it doesn't really matter where you put the include. maybe try result.sort.reverse ? not the best way, but probably the most straightforwardLabe
that gives me oranges first then apples, but i'm trying to get apples first sorted ascending, followed by oranges ascending. Still no luck. Anyone have any ideas?Redeploy
okay, so why is it that Array#sort does not use its own <=> method to perform the comparisons, but instead uses my overridden one?? Doesn't this violate ruby's dynamic method lookup?Redeploy
You are overriding <=> on fruity, not array.Labe
UPDATE: It turns out I was going about this the wrong way. Hint: the answer lies here -- blog.hasmanythrough.com/2008/8/17/sorting-things-outRedeploy
S
-1

Probably late, nevertheless...

add the following monkeypatch

class Array
  def to_i(default=Float::INFINITY)
    self.map do |element|
      element.nil? ? default : element.to_i
    end
  end
end

And change the body of Fruity::<=> to

[self.weight, self.sweetness].to_i <=> [other.weight, other.sweetness].to_i
Stirpiculture answered 1/5, 2013 at 14:2 Comment(1)
sorry, but this is bad advice. (monkey patching always is, but this problem can be solved a lot more elegantly). monkey patches like this can blow up a lot of other things.Spermatid

© 2022 - 2024 — McMap. All rights reserved.