Rubyesque way to do multiple <=>


#1

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.


#2

On 4/13/06, Victor S. removed_email_address@domain.invalid 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 :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

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

#3
[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.


#4

On 4/13/06, Victor S. removed_email_address@domain.invalid 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

#5

On 4/13/06, Victor S. removed_email_address@domain.invalid 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


#6

Victor S. wrote:

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

ComplexCustomComparatorn.compare(foo, other.foo)
end


#7

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.


#8

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.


#9

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


#10

On Fri, 2006-04-14 at 16:49 +0900, Victor S. 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.


#11

On 4/14/06, Victor S. removed_email_address@domain.invalid 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 :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

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