Hi Robert,
Hi all,
I've found that for the current project I am working on that I am
frequently
doing sorts based on multiple keys. The comparisons on each key tend to
vary, ie. sometimes I want descending rather than ascending, and other
times
the comparison itself is complex.
I usually do something like this:
foo.sort! {|a,b|
# Ascending, primary.
rv = a.key0<=> b.key0
# Descending, secondary.
rv = b.key1<=> a.key1 if rv == 0
# A tricky and expensive comparison, tertiary.
rv = a.magic(b) if rv == 0
rv
}
Does anyone have any suggestions as to a more elegant way to express
this?
I'm after something that is logically equivalent, but easier to write and
clearer to read.
A general way to perform a multi-key sort is:
# assuming sort() is a stable sort algorithm, e.g. merge sort
foo.sort! {|a,b|
# starting from the least important key
a.magic(b)
}
foo.sort! {|a,b|
# sort again using the second-least important key
b.key1<=> a.key1
}
# repeat until you are done with the most important key
I guess you can generalize this using an array of Lambdas.
One potential disadvantage of this for your particular case is that,
a.magic(b) is always performed even if a.key0 != b.key0. As you have
stated, this is quite expensive, and can make the above general scheme
much more expensive than your way.
This is totally inefficient because it goes through the original data
set (which might be large) multiple times. The complexity of #magic
only adds to this.
Very true.
Cool- thanks for that. I won't be able to use it directly as the last key
sort for most of the things I am doing is typically expensive, but I'm still
quite interested in different possible approaches. Thankyou. 
Please don't, there are much better approaches (see botp's for
example). Here are more
Yes, it's not directly suitable for what I'm doing but the different approach was interesting- there are cases where I might be able to have the input come in already sorted against one of the keys without cost. It got me thinking, anyway.
foo.sort! {|a,b|
# Ascending, primary.
(a.key0<=> b.key0).nonzer0? ||
# Descending, secondary.
(b.key1<=> a.key1).nonzero? ||
# A tricky and expensive comparison, tertiary.
a.magic(b)
}
Very, very neat; and nice find on "nonzero?".
My first thought was that "nonzero?" surely returns a boolean and this would break it- but it looks like "nonzero?" with numeric return was designed for this *exact* problem. 
I was wondering if there was a nice way to drop the "rv" from my solution- this looks like the best way.
foo.sort_by {|a|
[
# Ascending, primary.
a.key0,
# Descending, secondary, only if numeric.
-a.key1,
# A tricky and expensive comparison, tertiary.
a.create_magic_key
]
}
I'd stumbled on sort_by initially, I suspect I'll use it when I can precompute the comparisons easily (this is sometimes the case, sometimes not).
All these approaches can also be stored in a lambda and used from there
YourClass::SORT_FOO = lambda {|a| [a.key0, -a.key1, a.create_magic_key]}
YourClass::SORT_BAR = lambda {|a,b| (a.key0<=> b.key0).nonzero? || ... }
enum.sort_by&YourClass::SORT_FOO
enum.sort&YourClass::SORT_BAR
Excellent stuff. 
I'm not at all familiar with the "enum.sort_by&YourClass::SORT_FOO" syntax at this point, so I might need to read up on that. I can at least partly guess from the context though.
Thankyou for your very detailed input on this one, excellent stuff! 
Garth
···
On 19/01/12 19:52, Robert Klemme wrote:
On Thu, Jan 19, 2012 at 10:09 AM, Garthy D > <garthy_lmkltybr@entropicsoftware.com> wrote:
On 19/01/12 16:35, Yong Li wrote:
On Thu, Jan 19, 2012 at 8:58 AM, Garthy D >>> <garthy_lmkltybr@entropicsoftware.com> wrote: