Topic: Sorting an array??

I have trouble sorting an array of objects where I want to sort by whatever a method of each of these objects returns. The method name is not known in advance (since the members are ActiveRecord objects) nor is the type of the value which the method returns, nor can be guaranteed that the value will not be nil.

So I have

arr=[o1, o2, o3, o4]

and I want to sort by an attribute of these objects stored in the variable attr. Sometimes attr might contain "field1", then I want to sort the array by whatever o1.field1, o2.field1 etc. returns, sometimes attr might contain another field name.

I tried to do this with

arr.sort {|a,b| eval("a.#{attr}") <=> eval("b.#{attr}") }

but this does not work if the value returned is nil or true/false.

I then wrote a longer sort block that checks all possibilities for nil etc. and only uses <=> for non-nil and non-boolean values.

Now the next problem is that sorting does not seem to preserve order for equal elements -- how can one sort by a field1 so that for objects with equal values in field1, the sorting will be done by field2?

In perl, this is easy: since sort preserves order for equal elements, I first sort by field2, then field1. This does NOT seem to work in Ruby?

How can this be done in Ruby?

Last edited by johann_p (2007-06-04 15:58:29)

Re: Sorting an array??

Here's a few tips:

1. Use sort_by instead of sort, this is more efficient and easier to use.
2. Use "send" instead of eval.
3. You can place them in an array so the sorting takes place in that order.

arr.sort_by { |a| [a.send(attr1), a.send(attr2)] }

Railscasts - Free Ruby on Rails Screencasts

Re: Sorting an array??

ryanb wrote:

Here's a few tips:

1. Use sort_by instead of sort, this is more efficient and easier to use.
2. Use "send" instead of eval.
3. You can place them in an array so the sorting takes place in that order.

arr.sort_by { |a| [a.send(attr1), a.send(attr2)] }

Thanks for the tips.

The problem with this is that the sort again chokes on nil values.

However, the solution of comparing arrays that is implicitly used by your suggestion also works when I use it directly in sort, so I now do something like:

arr.sort do |a,b| 
  f1a=a.send(attr1)
  f1b=b.send(attr1)
  f2a=a.send(attr2)
  f2b=b.send(attr2)
  if(f1a == nil && f2a == nil)
    f2a <=> f2b # attr2 can never be nil
  elsif(
    # other cases that will not work with <=>
  else
    [f1a,f2a] <=> [f1b,f2b]
  end
end