On Wed, 14 Sep 2005, Hugh Sasse wrote:

> require 'set'
>
> class Student
>  attr_accessor :forename, :surname, :birth_dt,
>    :picture, :coll_status
>  def initialize(forename0, surname0, birth_dt0,
>                 picture0, coll_status0)
>    @forename = forename0
>    @surname = surname0
>    @birth_dt = birth_dt0
>    @picture = picture0
>    puts "in student.new() picture is #{picture0.inspect}, @picture is 
> #{@picture.inspect} " if $debug
>    @coll_status = coll_status0
>  end
>
>  def eql?(other)
>    # if self.forename == "John" and other.forename == "John"
>      debug = true
>    # end
>    res = [:forename, :surname, :birth_dt, :picture, :coll_status].all? do 
> |msg|
>      print "#{self.send(msg)} == #{(other.send(msg))} gives #{self.send(msg) 
> == (other.send(msg))}" if debug
>      self.send(msg) == (other.send(msg))
>    end
>    return res
>  end
>
>  def to_s
>    "#{@surname}, #{@forename}, #{@birth_dt}, #{@picture}, #{@coll_status}"
>  end
> end

well this works:

   s0 = Student::new 'a', 'b', 'c', 'd', 'e'
   s1 = Student::new 'a', 'b', 'c', 'd', 'e'
   p(s0.eql?(s1))  #=> true

but this doesn't

   p s0 == s1  #=> false

> And in the body of my program I read the records in from the csv and
> add the students if they are new. They tend to be clustered in the
> input, hence the last_student test.
>
> class TableMaker
>  INPUT = "hugh.csv"
>
>  ACCEPTED_MODULES =  /^\"TECH(100[1-7]|200\d|201[01]|300\d|301[0-2])/
>
>  # Read in the database and populate the tables.
>  def initialize(input=INPUT)
>
>    @students = Set.new()
>    # [...]
>    open(input, 'r') do |infp|
>      while record = infp.gets
>        record.chomp!

try : record.strip!

>        puts "record is #{record}"
>        forename, surname, birth_dt, institution_id, aos_code,
>          various, other, fields,
>          picture, coll_status, full_desc = record.split(/\s*\|\s*/)

or
       fields = record.split(%r/\|/).map{|field| field.strip}
       forename, surname, birth_dt, institution_id, aos_code,
       various, other, fields,
       picture, coll_status, full_desc =


if you don't do one of these two things the either

   - forname may have leading space
   - full_desc may have trailing space

that's because chomp! only blows away trailing newline - not extraneous
spaces and leading space on record is never dealt with.

>
>        next unless aos_code =~ ACCEPTED_MODULES
>
>        puts "from record, picture is [#{picture.inspect}]." if $debug
>        # Structures for student
>        student = Student.new(forename, surname, birth_dt, picture, 
> coll_status)
>        if student == last_student

so, as shown above, this (==) does not work

>          student = last_student
>        else
>          student.freeze
>
>          # Avoid duplicates
>          unless @students.include? student
>            @students.add student
>          end
>          last_student = student
>        end
>        # [...]
>      end
>    end
>  end
>
>  # [...]
>
> end
>
>
> This being a Set I don't really need the call to include? now, but
> it's there (from when I was using a hash for this).
>
> I find two things that seem odd to me:
>
> 1. eql? is never getting called, despite include?.

set uses Object#hash - so maybe something like (untested)

   class Student
     def hash
       %w( forename surname birth_dt picture coll_status).inject(0){|n,m| n += send(m).hash}
     end
   end

i dunno if this will wrap and cause issues though...

if so maybe something like

   class Student
     def hash
       %w( forename surname birth_dt picture coll_status).map{|m| send %m}.join.hash
     end
   end

or, perhaps simple something like:

   class Student < ::Hash
     FIELDS = %w( forename surname birth_dt picture coll_status )
     def initialize(*fs)
       FIELDS.each do |f|
         self[f] = (fs.shift || raise(ArgumentError, "no #{ f }!"))
       end
     end
     def eql? other
       values == other.values
     end
     alias == eql?
     def keys
       FIELDS
     end
     def values
       values_at(*FIELDS)
     end
     def hash
       FIELDS.map{|m| self[m]}.join.hash
     end
   end

   s0 = Student::new 'a', 'b', 'c', 'd', 'e'
   s1 = Student::new 'a', 'b', 'c', 'd', 'e'

   require 'set'
   set = Set::new
   set.add s0
   set.add s1
   p set #=> #<Set: {{"forename"=>"a", "coll_status"=>"e", "birth_dt"=>"c", "picture"=>"d", "surname"=>"b"}}>

the FIELDS const can be used to do ordered prints, etc.

it sure seems odd that set doesn't use 'eql?' or '==' up front though doesn't
it?

-a
-- 
===============================================================================
| email :: ara [dot] t [dot] howard [at] noaa [dot] gov
| phone :: 303.497.6469
| Your life dwells amoung the causes of death
| Like a lamp standing in a strong breeze.  --Nagarjuna
===============================================================================