Rubyesque way to do multiple <=>

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.

about the second.
Run the following code and you will clearly see why :wink:
------------------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

···

On 4/13/06, Victor Shepelev <vshepelev@imho.com.ua> wrote:

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.

Actually you just use your first version of method <=> without worrying

--
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 wrote:

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?

def <=> (other)
  ComplexCustomComparator1.compare(sex, other.sex).nonzero? ||
  ComplexCustomComparator2.compare(age, other.age).nonzero? ||
  # ...
  ComplexCustomComparatorn.compare(foo, other.foo)
end

> 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.

Actually you just use your first version of method <=> without worrying
about the second.
Run the following code and you will clearly see why :wink:
------------------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

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.

>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?
>
>
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.

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

···

On 4/13/06, Victor Shepelev <vshepelev@imho.com.ua> wrote:

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

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

···

On 4/13/06, Victor Shepelev <vshepelev@imho.com.ua> wrote:

> > 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.

> Actually you just use your first version of method <=> without worrying
> about the second.
> Run the following code and you will clearly see why :wink:
> ------------------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

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.

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.

···

On Fri, 2006-04-14 at 16:49 +0900, Victor Shepelev wrote:

> >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?
> >
> >
>

--
Ross Bamford - rosco@roscopeco.REMOVE.co.uk

Victor Shepelev schrieb:

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?

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.

from class Numeric - RDoc Documentation

···

---------------------------------------------------------------------
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

> 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 :slight_smile:
If my uppercased words have hurt your feelings, I must apoligize :frowning:
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" :slight_smile:
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.

> > 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 :slight_smile:

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 :frowning:

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" :slight_smile:
Sorry my bad manners, please

See above, no pbm here

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.

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

···

On 4/14/06, Victor Shepelev <vshepelev@imho.com.ua> wrote:

--
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