Forum: Ruby Rubyesque way to do multiple <=>

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
Victor Shepelev (Guest)
on 2006-04-13 21:51
(Received via mailing list)
Hello All.


I have class with some fields

class Human
  attr_accessor :sex, :age, :second_name, :first_name
end

now I want to sort objects of this class. I've defined operator <=>

class Human
  def <=>(other)
    [sex, age, second_name, first_name] <=> [sex, age, second_name,
first_name]
  end
end

Looks good, yeah? But! If I use third-party complex comparator for some
of
the fields, I receive something like:

def <=> (other)
  res = ComplexCustomComparator.compare(sex, other.sex)
  return res if res != 0

  #if they have the same sex, do other comparisons
end

Foooooo!

How can I do the former in more elegant way?

Thanks.

Victor.
Robert Dober (Guest)
on 2006-04-13 22:37
(Received via mailing list)
On 4/13/06, Victor Shepelev <vshepelev@imho.com.ua> wrote:
> now I want to sort objects of this class. I've defined operator <=>
>
>
> Thanks.
>
> Victor.
>
>
> Actually you just use your first version of method <=> without worrying
about the second.
Run the following code and you will clearly see why ;)
------------------8<-------------------------------
class A
    def <=>(other)
        puts "<=> in A"
        0
    end
end

puts "If at first you do not succeed"
[1, A.new] <=> [2, A.new]
puts "..."
[A.new ] <=> [A.new]
----------------------->8----------------------------

Ruby takes care of everything for you

Cheers
Robert
--
Deux choses sont infinies : l'univers et la bêtise humaine ; en ce qui
concerne l'univers, je n'en ai pas acquis la certitude absolue.

- Albert Einstein
Victor Shepelev (Guest)
on 2006-04-13 22:52
(Received via mailing list)
> >     [sex, age, second_name, first_name] <=> [sex, age, second_name,
> >   return res if res != 0
> > Victor.
>
> puts "If at first you do not succeed"
> [1, A.new] <=> [2, A.new]
> puts "..."
> [A.new ] <=> [A.new]
> ----------------------->8----------------------------
>
> Ruby takes care of everything for you

You haven't got it. I already USED first version, but I CAN'T if
comparison
is done through stand-alone function instead of <=>

> Cheers
> Robert

Victor.
Robert Dober (Guest)
on 2006-04-13 23:02
(Received via mailing list)
On 4/13/06, Victor Shepelev <vshepelev@imho.com.ua> wrote:
>
>
>
> You haven't got it. I already USED first version, but I CAN'T if
> comparison
> is done through stand-alone function instead of <=>


I have not, you are right! really sorry, I just *could* not immagine
that
someone would implement a Custom Compare without defining <=>.
But as it is you have of course a problem.
Forgive my answer sometimes there are beginners posting.
The next time however, before replying, I might read the question.

What you have done is nice, but condider this too in case you can
determine
a reasonable BaseClass of sex.

class SexBaseClass
   def <=>(other)
      ComplexCompareFunction self, other
   end
end

Hope I got it
Robert

> Cheers
> > Robert
>
> Victor.
>
>
>


--
Deux choses sont infinies : l'univers et la bêtise humaine ; en ce qui
concerne l'univers, je n'en ai pas acquis la certitude absolue.

- Albert Einstein
Patrick Hurley (Guest)
on 2006-04-13 23:23
(Received via mailing list)
On 4/13/06, Victor Shepelev <vshepelev@imho.com.ua> wrote:
> > >     [sex, age, second_name, first_name] <=> [sex, age, second_name,
> > >   return res if res != 0
> > > Victor.
> >     end
> You haven't got it. I already USED first version, but I CAN'T if comparison
> is done through stand-alone function instead of <=>
>
> > Cheers
> > Robert
>
> Victor.
>
>
>

Can you provide a <=> for the special cases? Otherwise I guess
something like this:

class Array
  def fancy_cmp(other, sym = :<=>)
    each_with_index do |val,i|
      result = val.send(sym, other[i])
      return result unless result.zero?
    end
    self.size <=> other.size
  end
end

Might help
pth
Florian Frank (Guest)
on 2006-04-14 05:29
(Received via mailing list)
Victor Shepelev wrote:

>
>
def <=> (other)
  ComplexCustomComparator1.compare(sex, other.sex).nonzero? ||
  ComplexCustomComparator2.compare(age, other.age).nonzero? ||
  # ...
  ComplexCustomComparatorn.compare(foo, other.foo)
end
Victor Shepelev (Guest)
on 2006-04-14 09:29
(Received via mailing list)
> > You haven't got it. I already USED first version, but I CAN'T if
> > comparison
> > is done through stand-alone function instead of <=>
>
>
> I have not, you are right! really sorry, I just *could* not immagine that
> someone would implement a Custom Compare without defining <=>.
> But as it is you have of course a problem.
> Forgive my answer sometimes there are beginners posting.
> The next time however, before replying, I might read the question.

I thinks, there is a bit of irony :)
If my uppercased words have hurt your feelings, I must apoligize :(
Uppercase was only logical stress (moreover, my English is not so good,
so
maybe I said something terrible, but I have no intention to show my
"great
mind" :)
Sorry my bad manners, please

> What you have done is nice, but condider this too in case you can
> determine
> a reasonable BaseClass of sex.
>
> class SexBaseClass
>    def <=>(other)
>       ComplexCompareFunction self, other
>    end
> end

Yes, I know this way, but I really can't use it. Here is more real
example:

class Product
  attr_accessor :type, :field1, :field2, field3
end

If @type is "HDD", @field1 is string representing hdd size (120 Gb, 80
Gb
and so on): comparison must be done as field1.to_i <=> other.field1.to_i

If @type is "drive", @field1 is drive type ("cd-r", "cd-rw", "dvd" ...)
and
comparison must be done in a strange way ("cd-XXX" > "dvd-XXXX", but
"cd-rom" < "cd-r" < "cd-rw").

OK, you would ask, why I've used one "general" class Product instead of
concrete HDD, CPU, Drive and so on? Because product type can be changed
dynamically (it is guessed by product full name, on wrong guess user can
change @type manually through GUI).

So, my question is still actual.

> Hope I got it
> Robert

Victor.
Victor Shepelev (Guest)
on 2006-04-14 09:51
(Received via mailing list)
> >
> >
> def <=> (other)
>   ComplexCustomComparator1.compare(sex, other.sex).nonzero? ||
>   ComplexCustomComparator2.compare(age, other.age).nonzero? ||
>   # ...
>   ComplexCustomComparatorn.compare(foo, other.foo)
> end

Seems that return value would be wrong (true/false instead of -1|0|1)

Victor.
Ross Bamford (Guest)
on 2006-04-14 10:40
(Received via mailing list)
On Fri, 2006-04-14 at 16:49 +0900, Victor Shepelev wrote:
> > >
> > >
> >

How about something like (untested):

def <=>(other)
  [:name, :age, :sex].map do |attr,comp|
    ComplexCustomComparator.compare(send(attr), other.send(attr))
  end.find { |e| e.nonzero? } || 0
end

Or if you have to use a different comparator for each attribute:

def somewhere_else_eg_initialize
  @comparators = [NameComparator.new,
                  AgeComparator.new,
                  SexComparator.new]
end

def <=>(other)
  [:name, :age, :sex].zip(@comparators).map do |attr,comp|
    comp.compare(send(attr), other.send(attr))
  end.find { |e| e.nonzero? } || 0
end

Probably not the most efficient way (two iterations) but it should work
I think.
Simon Kröger (Guest)
on 2006-04-14 12:49
(Received via mailing list)
Victor Shepelev schrieb:
>>>
> Victor.
from http://www.ruby-doc.org/core/classes/Numeric.html#M001317
---------------------------------------------------------------------
num.nonzero? => num or nil

Returns num if num is not zero, nil otherwise. This behavior is useful
when chaining comparisons:

   a = %w( z Bb bB bb BB a aA Aa AA A )
   b = a.sort {|a,b| (a.downcase <=> b.downcase).nonzero? || a <=> b }
   b   #=> ["A", "a", "AA", "Aa", "aA", "BB", "Bb", "bB", "bb", "z"]

---------------------------------------------------------------------

cheers

Simon
Robert Dober (Guest)
on 2006-04-14 14:13
(Received via mailing list)
On 4/14/06, Victor Shepelev <vshepelev@imho.com.ua> wrote:
> > someone would implement a Custom Compare without defining <=>.
> > But as it is you have of course a problem.
> > Forgive my answer sometimes there are beginners posting.
> > The next time however, before replying, I might read the question.
>
> I thinks, there is a bit of irony :)


Not at all, I do not like UPPERCASE but I did not notice, I was too
surprised by the fact that I indeed had missed  the point.

If my uppercased words have hurt your feelings, I must apoligize :(
> Uppercase was only logical stress (moreover, my English is not so good, so
> maybe I said something terrible, but I have no intention to show my "great
> mind" :)
> Sorry my bad manners, please


See above, no pbm here

> Yes, I know this way, but I really can't use it. Here is more real
> and
> > Hope I got it
> > Robert
>
> Victor.
>
>
> Ok if you can define a method <=> for all your objects one way or the
other you are fine, if you cannot, you have to use the little extra code
you
have written in the first place.
Please do not stress about this because your original code is very
correct.
We are just pondering matters of philosophy here, right?

Happy Easter
Robert


--
Deux choses sont infinies : l'univers et la bêtise humaine ; en ce qui
concerne l'univers, je n'en ai pas acquis la certitude absolue.

- Albert Einstein
This topic is locked and can not be replied to.