Is there a shorter way to compare these 2 objects?

(Lowell Kirsh) #1

I have a class called foo and I want to make it comparable so that I can sort arrays of Foo. Here is the class:

class Foo
  attr_accessor :a, :b, :c, :d
  def <=>(rhs); (see below); end
end

I'm assuming that the a,b,c, and d field are each Comparable. To compare 2 Foos, we first look at the a's and if they're not the same, we return the result of their comparison. If they are the same, we continue to compare the b's, and if they're the same we compare the c's, and so on until we compare the last field. So for my definition of <=> I have the following, which works, but I'd like something better/shorter:

if (@a <=> rhs.a) != 0
  @a <=> rhs
elsif (@b <=> rhs.b) != 0
  @b <=> rhs.b
elsif ....

Is there a more concise way of doing this? I know this code isn't the ugliest, but something in my gut tells me there's got to be a better way. Especially if we have more than 4 variables.

lowell

(James Edward Gray II) #2

def <=>( other )
   [@a, @b, @c, @d] <=> [other.a, other.b, other.c, other.d]
end

Arrays are Comparable and they compare each of their contents in order.

Hope that helps.

James Edward Gray II

···

On Aug 17, 2005, at 10:16 PM, Lowell Kirsh wrote:

I have a class called foo and I want to make it comparable so that I can sort arrays of Foo. Here is the class:

class Foo
    attr_accessor :a, :b, :c, :d
    def <=>(rhs); (see below); end
end

I'm assuming that the a,b,c, and d field are each Comparable. To compare 2 Foos, we first look at the a's and if they're not the same, we return the result of their comparison. If they are the same, we continue to compare the b's, and if they're the same we compare the c's, and so on until we compare the last field. So for my definition of <=> I have the following, which works, but I'd like something better/shorter:

(Robert) #3

James Edward Gray II wrote:

I have a class called foo and I want to make it comparable so that
I can sort arrays of Foo. Here is the class:

class Foo
    attr_accessor :a, :b, :c, :d
    def <=>(rhs); (see below); end
end

I'm assuming that the a,b,c, and d field are each Comparable. To
compare 2 Foos, we first look at the a's and if they're not the
same, we return the result of their comparison. If they are the
same, we continue to compare the b's, and if they're the same we
compare the c's, and so on until we compare the last field. So for
my definition of <=> I have the following, which works, but I'd
like something better/shorter:

def <=>( other )
   [@a, @b, @c, @d] <=> [other.a, other.b, other.c, other.d]
end

Arrays are Comparable and they compare each of their contents in
order.

Hope that helps.

James Edward Gray II

Here are some other possible implementations using #inject... :slight_smile:

require 'enumerator'

Foo = Struct.new :a,:b,:c,:d

class Foo
  include Comparable
  
  def <=>(o)
    members.inject(0) do |cmp,field|
      return cmp unless cmp == 0
      send(field) <=> o.send(field)
    end
  end
  
  # alternative
  def <=>(o)
    cmp = 0
    each_pair do |field, val|
      cmp = val <=> o.send(field)
      return cmp unless cmp == 0
    end
    cmp
  end
  
  # alternative
  def <=>(o)
    to_enum(:each_pair).inject(0) do |cmp,(field, val)|
      cmp = val <=> o.send(field)
      return cmp unless cmp == 0
    end
  end

end

f1 = Foo.new 1,2,3,4

=> #<struct Foo a=1, b=2, c=3, d=4>

f2 = Foo.new 1,2,3,3

=> #<struct Foo a=1, b=2, c=3, d=3>

puts f1 <=> f2

1
=> nil

Kind regards

    robert

···

On Aug 17, 2005, at 10:16 PM, Lowell Kirsh wrote:

(James Edward Gray II) #4

I misspoke a little here. Arrays are NOT Comparable, but the DO implement <=>(), which is all you need here.

James Edward Gray II

···

On Aug 17, 2005, at 10:33 PM, James Edward Gray II wrote:

Arrays are Comparable...

(Lowell Kirsh) #5

James Edward Gray II wrote:

def <=>( other )
  [@a, @b, @c, @d] <=> [other.a, other.b, other.c, other.d]
end

Great, this is *exactly* what I was looking for :slight_smile:

(Lowell Kirsh) #6

Those solutions are definitely cool but I find James' to be more readable so that's what I'll use.

Robert Klemme wrote:

···

require 'enumerator'

Foo = Struct.new :a,:b,:c,:d

class Foo
  include Comparable
    def <=>(o)
    members.inject(0) do |cmp,field|
      return cmp unless cmp == 0
      send(field) <=> o.send(field)
    end
  end
    # alternative
  def <=>(o)
    cmp = 0
    each_pair do |field, val|
      cmp = val <=> o.send(field)
      return cmp unless cmp == 0
    end
    cmp
  end
    # alternative
  def <=>(o)
    to_enum(:each_pair).inject(0) do |cmp,(field, val)|
      cmp = val <=> o.send(field)
      return cmp unless cmp == 0
    end
  end

end

(Robert) #7

Lowell Kirsh wrote:

Those solutions are definitely cool but I find James' to be more
readable so that's what I'll use.

Yes definitely! I just had some fun playing around with this. :slight_smile:

    robert