ruby picture

RCR 293: Easy definition of key and sort attributes of a class

Submitted by Robert (Fri Feb 18 07:02:01 UTC 2005)

Abstract

Often you want only a subset of the attributes of a Struct or other class be used for evaluation of equivalence and hash keys. Same applies for sort attributes. This RCR suggests to add two methods to Module that make this easier.

Problem

When creating a Struct all fields are used for sorting, comaprison and hashing while sometimes you just want a subset of these fields. Struct automatically includes all fields in comparison and hash while a user branded class does not automatically override #hash, #== and #eql?.

Proposal

Add two methods key_attributes and sort_attributes to class Module that generate #==, #eql? and #hash from key attributes and #<=> from sort attributes.

Analysis

The problem occurs with modest frequency and is general enough to be placed in the std lib. No imcompatibilities.

Implementation

class Module
  def key_attributes(*fields)
    code = ""
    code << "def ==(o) " << fields.map {|f| "self.#{f} == o.#{f}" }.join(" && ") << " end\n"
    code << "def eql?(o) " << fields.map {|f| "self.#{f}.eql?(o.#{f})" }.join(" && ") << " end\n"
    code << "def hash() " << fields.map {|f| "self.#{f}.hash" }.join(" ^ ") << " end\n"
    # puts code
    class_eval code
    fields
  end
  def sort_attributes(*fields)
    code = fields.inject("def <=>(o)\n") {|s,f| s << "cmp = self.#{f} <=> o.#{f}; return cmp unless cmp == 0\n" } << "0\nend"
    # puts code
    class_eval code
    fields
  end
end

Foo = Struct.new(:name, :age, :info)

class Foo

  key_attributes :name, :age
  sort_attributes :name, :info, :age
end

f = [

  Foo.new( "x", 1, "aaa" ),
  Foo.new( "x", 1, "bbb" ),
  Foo.new( "x", 2 , "aaa" ),
  Foo.new( "y", 1 , "aaa"),
]

p f.sort

f.each do |f1|

  f.each do |f2|
    puts "#{f1.inspect} ==     #{f2.inspect} : #{f1==f2}"
    puts "#{f1.inspect} eql?   #{f2.inspect} : #{f1.eql? f2}"
    puts "#{f1.inspect} equal? #{f2.inspect} : #{f1.equal? f2}"
  end
  puts
end

ruby picture
Comments Current voting
I think these are fair, but I maybe they are better just a requirable add-ons?

FYI, I've added these methods to Ruby Facets.

Thanks.

~Trans


It's funny, I thought about this exact thing and googled to see if anyone had implemented this or if it was already in the language and I just wasn't aware...and I stumble into the RCR.

Yes. I have to strongly advocate this. Particularly on the sorting issue. It's a lot cleaner than using the Comparable mixin and definining <=> for specifying sort criteria for a class.

Particularly with how often one works with an array of objects. (Which, yes...in Ruby is technically any time that you have an array period.)


I was looking at this again and thinking the implementation might be a perfect candidate for parameterized modules.

T.


Change the hashing to linear congruential random number generator (LCR), for example:

"def hash()\n h=1\n" << fields.map {|f| " h=((h * 31) ^ self.#{f}.hash) & 0xFFFF_FFFF" }.join("\n") << "\n h\nend\n"

(suggested by Hugh Sasse)


In the linear congruential version above, I now think that we DO need % instead of & , and that the number should be 0x7FFF_FFFF to fit in Fixnum on most present-day systems (1 bit less than word size).


Strongly opposed 0
Opposed 0
Neutral 1
In favor 4
Strongly advocate 2
ruby picture
If you have registered at RCRchive, you may now sign in below. If you have not registered, you may sign up for a username and password. Registering enables you to submit new RCRs, and vote and leave comments on existing RCRs.
Your username:
Your password:

ruby picture

Powered by .