[SORT?] by x,y allowing x,y to be passed as parms

Question: Are there better ways to do this
sort within the container class?

Have class CD with several fields defined,
@list_of_CDs array in class CD_List holding CD pointers,
and a couple of methods to sort array of pointers by any two fields.

Below is relevant class entries and code.

class CD
  attr_reader :size, :cusip, :issuer, :matures, ...
....
class CD_List
  @@sortkey1= @@sortkey2 = ""
  def initialize @list_of_CDs = Array.new end
  ...
  def new_ord(key1='size',key2='matures') #Provide defaults
    @@sortkey1, @@sortkey2 = key1, key2 # Set class variables
    @list_of_CDs.sort! { |a,b| cmp_item(a,b) } # Sort in place
  end
  def cmp_item (a,b) # Adapted from Ryan Pavlik
    # Allow different sort fields via Class variables
    f1 = eval('a.'+@@sortkey1) <=> eval('b.'+@@sortkey1)
    f2 = eval('a.'+@@sortkey2) <=> eval('b.'+@@sortkey2)
    (f1 == 0 ? f2 : f1)
  end
....
listCD = CD_List.new # Initialize it
....
listCD.new_ord('matures','cusip') # Sort list of CDs
__END__

Complete script with test data (143 lines) at:
http://jmauney.freeshell.org/cgi-bin/data/CD_sort2.rb.txt
Comments on entire script also solicited.

Have searched this newsgroup, read FAQ at RubyCentral,
read HTML of Pickax ver. 1, and Googled without success.
Newest Pickax on order.

Using ruby 1.8.2 (2004-12-25) [i386-mswin32]
Email address is _invalid_! Read here; please respond here.

harp:~ > cat a.rb
   class CD
     ATTRIBUTES = %w(
       size
       cusip
     )
     ATTRIBUTES.each{|a| attr_accessor a}
     def initialize hash
       hash.each{|k,v| send "#{ k }=", v}
     end
     def inspect
       'cd(' << ATTRIBUTES.map{|k| [k,send(k)].join('=')}.join(', ') << ')'
     end
   end

   class CDList < ::Array
     def sorted_by *keys
       block = lambda{|cd| keys.flatten.map{|k| cd.send k}}
       sort_by &block
     end
   end

   class Fixnum
     def of
       ret = ; times{|i| ret << yield(i) }; ret
     end
   end

   a = CD::new 'size' => rand, 'cusip' => rand
   b = CD::new 'size' => a.size, 'cusip' => rand

   c = CD::new 'size' => rand, 'cusip' => rand
   d = CD::new 'size' => rand, 'cusip' => c.cusip

   cdlist = CDList[ a, b, c, d]

   [ %w(size), %w(cusip), %w(size cusip), %w(cusip size) ].each do |keys|
     puts(('-' * 42), "sorted by <#{ keys.join ',' }>", ('-' * 42))
     p cdlist.sorted_by(keys)
     puts
   end

   harp:~ > ruby a.rb

···

On Mon, 24 Oct 2005, New 2 Ruby wrote:

Question: Are there better ways to do this
sort within the container class?

Have class CD with several fields defined,
@list_of_CDs array in class CD_List holding CD pointers,
and a couple of methods to sort array of pointers by any two fields.

Below is relevant class entries and code.

class CD
attr_reader :size, :cusip, :issuer, :matures, ...
....
class CD_List
@@sortkey1= @@sortkey2 = ""
def initialize @list_of_CDs = Array.new end
...
def new_ord(key1='size',key2='matures') #Provide defaults
   @@sortkey1, @@sortkey2 = key1, key2 # Set class variables
   @list_of_CDs.sort! { |a,b| cmp_item(a,b) } # Sort in place
end
def cmp_item (a,b) # Adapted from Ryan Pavlik
   # Allow different sort fields via Class variables
   f1 = eval('a.'+@@sortkey1) <=> eval('b.'+@@sortkey1)
   f2 = eval('a.'+@@sortkey2) <=> eval('b.'+@@sortkey2)
   (f1 == 0 ? f2 : f1)
end
....
listCD = CD_List.new # Initialize it
....
listCD.new_ord('matures','cusip') # Sort list of CDs
__END__

Complete script with test data (143 lines) at:
http://jmauney.freeshell.org/cgi-bin/data/CD_sort2.rb.txt
Comments on entire script also solicited.

Have searched this newsgroup, read FAQ at RubyCentral,
read HTML of Pickax ver. 1, and Googled without success.
Newest Pickax on order.

Using ruby 1.8.2 (2004-12-25) [i386-mswin32]
Email address is _invalid_! Read here; please respond here.

   ------------------------------------------
   sorted by <size>
   ------------------------------------------
   [cd(size=0.0527311303740203, cusip=0.221851657904869), cd(size=0.603176361108372, cusip=0.221851657904869), cd(size=0.745173353626499, cusip=0.899297785462137), cd(size=0.745173353626499, cusip=0.83329161533014)]

   ------------------------------------------
   sorted by <cusip>
   ------------------------------------------
   [cd(size=0.0527311303740203, cusip=0.221851657904869), cd(size=0.603176361108372, cusip=0.221851657904869), cd(size=0.745173353626499, cusip=0.83329161533014), cd(size=0.745173353626499, cusip=0.899297785462137)]

   ------------------------------------------
   sorted by <size,cusip>
   ------------------------------------------
   [cd(size=0.0527311303740203, cusip=0.221851657904869), cd(size=0.603176361108372, cusip=0.221851657904869), cd(size=0.745173353626499, cusip=0.83329161533014), cd(size=0.745173353626499, cusip=0.899297785462137)]

   ------------------------------------------
   sorted by <cusip,size>
   ------------------------------------------
   [cd(size=0.0527311303740203, cusip=0.221851657904869), cd(size=0.603176361108372, cusip=0.221851657904869), cd(size=0.745173353626499, cusip=0.83329161533014), cd(size=0.745173353626499, cusip=0.899297785462137)]

hth.

-a
--

email :: ara [dot] t [dot] howard [at] noaa [dot] gov
phone :: 303.497.6469
anything that contradicts experience and logic should be abandoned.
-- h.h. the 14th dalai lama

===============================================================================

Hi --

Question: Are there better ways to do this
sort within the container class?

I see class variables and eval, so I will go out on a limb and say:
yes :slight_smile:

Have class CD with several fields defined,
@list_of_CDs array in class CD_List holding CD pointers,
and a couple of methods to sort array of pointers by any two fields.

Below is relevant class entries and code.

class CD
attr_reader :size, :cusip, :issuer, :matures, ...
....
class CD_List
@@sortkey1= @@sortkey2 = ""
def initialize @list_of_CDs = Array.new end
...
def new_ord(key1='size',key2='matures') #Provide defaults
   @@sortkey1, @@sortkey2 = key1, key2 # Set class variables
   @list_of_CDs.sort! { |a,b| cmp_item(a,b) } # Sort in place
end
def cmp_item (a,b) # Adapted from Ryan Pavlik
   # Allow different sort fields via Class variables
   f1 = eval('a.'+@@sortkey1) <=> eval('b.'+@@sortkey1)
   f2 = eval('a.'+@@sortkey2) <=> eval('b.'+@@sortkey2)
   (f1 == 0 ? f2 : f1)
end

One possibility would be to have CD objects themselves know how to
compare themselves with each other via a <=> method, though then you'd
have to have them know how to change that which might be slow and
cumbersome.

If you want to keep the sorting intelligence in the CD_List object, I
would do it more simply. You don't actually need class variables
anywhere you've used them. Possibly instance variables... but
possibly not anything, unless you really need to save the sort keys
beyond the sort operation for some reason.

Here's a little mini-version that doesn't do much but might give you
some ideas. (You could also have CD_List inherit from Array, though
inheriting from base classes is often fraught with unexpected
problems.)

   class CD
     attr_reader :size, :cusip
     def initialize(size,cusip)
       @size,@cusip = size,cusip
     end
   end

   class CD_List
     def initialize
       @cds =
     end

     def add(cd)
       @cds << cd
     end

     def new_ord(key1='size',key2='cusip')
       @cds.replace(@cds.sort_by {|cd| [cd.send(key1), cd.send(key2)] })
     end
   end

   c = CD.new(10,"abc")
   d = CD.new(5,"def")
   e = CD.new(5,"bcd")

   cdl = CD_List.new
   cdl.add(c)
   cdl.add(d)
   cdl.add(e)

   p cdl.new_ord

David

···

On Mon, 24 Oct 2005, New 2 Ruby wrote:

--
David A. Black
dblack@wobblini.net

Ara.T.Howard wrote:

<snip/>

   harp:~ > cat a.rb
   class CD
     ATTRIBUTES = %w(
       size
       cusip
     )
     ATTRIBUTES.each{|a| attr_accessor a}
     def initialize hash
       hash.each{|k,v| send "#{ k }=", v}
     end
     def inspect
       'cd(' << ATTRIBUTES.map{|k| [k,send(k)].join('=')}.join(', ')
     << ')' end
   end

   class CDList < ::Array
     def sorted_by *keys
       block = lambda{|cd| keys.flatten.map{|k| cd.send k}}
       sort_by &block
     end
   end

Why not directly use the block? I think it's a tad more efficient:

def sort_by(*keys)
  sort_by {|x| keys.map {|k| x.send k}}
end

   class Fixnum
     def of
       ret = ; times{|i| ret << yield(i) }; ret
     end
   end

Alternative

def of(&b) (0...self).map(&b) end

But what do you need that for? I couldn't find it...

<snip/>

Kind regards

    robert

Thanks Ara. Yes, that will sort on one or more of the
attributes of CD. Now, will work on understanding how it does it.

Jim

Email address is _invalid_! Read here; please respond here.

···

On Sun, 23 Oct 2005 19:02:37 -0600, "Ara.T.Howard" <Ara.T.Howard@noaa.gov> wrote:

On Mon, 24 Oct 2005, New 2 Ruby wrote:

Have class CD with several fields defined,
@list_of_CDs array in class CD_List holding CD pointers,
and a couple of methods to sort array of pointers by any two fields.

    def initialize hash
      hash.each{|k,v| send "#{ k }=", v}
    end
    def inspect
      'cd(' << ATTRIBUTES.map{|k| [k,send(k)].join('=')}.join(', ') << ')'
    end

  class CDList < ::Array
    def sorted_by *keys
      block = lambda{|cd| keys.flatten.map{|k| cd.send k}}
      sort_by &block

Hi --

Question: Are there better ways to do this
sort within the container class?

    def new_ord(key1='size',key2='cusip')
      @cds.replace(@cds.sort_by {|cd| [cd.send(key1), cd.send(key2)] })
    end

....

  p cdl.new_ord

Aah, thank you. Just what the Dr. ordered. Adding a pinch of Ara
Howard to generalize for n attributes:

def new_ord(*args)
   @cds.replace(@cds.sort_by {|cd| args.map {|a| cd.send a }})
end
....
p cdl.new_ord('size','cusip')

Email address is _invalid_! Read here; please respond here.

···

On Mon, 24 Oct 2005 11:27:35 +0900, "David A. Black" <dblack@wobblini.net> wrote:

On Mon, 24 Oct 2005, New 2 Ruby wrote:

Why not directly use the block? I think it's a tad more efficient:

<snip>

def of(&b) (0...self).map(&b) end

But what do you need that for? I couldn't find it...

yeah - remnants of partial solution on both counts. should've cleaned it up a
bit more first :wink:

the 'of' method is one of my favouraites - i think i have an RCR out there for
it...

regards.

-a

···

On Mon, 24 Oct 2005, Robert Klemme wrote:
--

email :: ara [dot] t [dot] howard [at] noaa [dot] gov
phone :: 303.497.6469
anything that contradicts experience and logic should be abandoned.
-- h.h. the 14th dalai lama

===============================================================================

Ara.T.Howard wrote:

Why not directly use the block? I think it's a tad more efficient:

<snip>

def of(&b) (0...self).map(&b) end

But what do you need that for? I couldn't find it...

yeah - remnants of partial solution on both counts. should've
cleaned it up a bit more first :wink:

Ah, I see. Suspected already that you were storing the block for later
(re)use...

the 'of' method is one of my favouraites - i think i have an RCR out
there for it...

Ah, then this was a case of surreptitious advertising. :slight_smile: Hmmm, can't
seem to find it here
ahoward's RCR activity What's the idea behind it?

Kind regards

    robert

···

On Mon, 24 Oct 2005, Robert Klemme wrote:

hmmm. i can't find it either. the idea is

   a, b, c = 3.of{ Hash::new }

   lists = 42.of{ }

   data = 6.of{ rand }

-a

···

On Mon, 24 Oct 2005, Robert Klemme wrote:

the 'of' method is one of my favouraites - i think i have an RCR out there
for it...

Ah, then this was a case of surreptitious advertising. :slight_smile: Hmmm, can't
seem to find it here
ahoward's RCR activity What's the idea behind it?

--

email :: ara [dot] t [dot] howard [at] noaa [dot] gov
phone :: 303.497.6469
anything that contradicts experience and logic should be abandoned.
-- h.h. the 14th dalai lama

===============================================================================

Read the mailing list long enough and cool little trick inevitabely pop
up.
That could be pretty handy,
  .adam