Robert Klemme
10/4/2004 1:57:00 PM
"Gavin Kistner" <gavin@refinery.com> schrieb im Newsbeitrag
news:E16B31E1-1605-11D9-AE46-000A959CF5AC@refinery.com...
> On Oct 4, 2004, at 3:14 AM, Robert Klemme wrote:
> >> a.sort { |x,y| x[3] <=> y[3] } # sort on 4th element
> > That's even better:
> > a.sort_by {|row| row[3]}
>
> The reason that #sort_by is better than #sort is described in the
> documentation. In short (IIRC) it's because it caches the key values
> for each row (performing the block only once for each row) and uses
> those to sort, rather than invoking the block for every unique pair it
> has to compare.
Yep. Plus, it's shorter to type, which was the main advantage I wanted to
point out here. :-)
> A downside, however, is the inability to specify something like a
> reverse sort. (Although for that case you can just reverse the array
> afterwards.)
>
> Actually, does anyone have a compelling example where #sort produce a
> result (which is reasonable) which #sort_by cannot? (By 'reasonable' I
> mean that something like a.sort{ |x,y| x[3] <=> y[7] } does something
> impossible for #sort_by, but probably would produce erratic results,
> given no way of knowing in which order the pairs come.)
The only one that I can think of at the moment is the one where there is
no proper comparision op defined. This is a made up example:
>> El = Struct.new(:foo, :bar)
=> El
>> El.new <=> El.new
NoMethodError: undefined method `<=>' for #<struct El foo=nil, bar=nil>
from (irb):3
Ok, no El.<=>() defined.
>> values = (1..10).map { El.new(rand(10), rand(10)) }
=> [#<struct El foo=6, bar=7>, #<struct El foo=5, bar=9>, #<struct El
foo=4, bar=8>, #<struct El foo=8, bar=9>, #<struct El foo=8, bar=7>,
#<struct El
foo=2, bar=2>, #<struct El foo=4, bar=5>, #<struct El foo=3, bar=5>,
#<struct El foo=8, bar=2>, #<struct El foo=1, bar=2>]
>> values.sort {|a,b| c = a.foo <=> b.foo; c == 0 ? a.bar <=> b.bar : c}
=> [#<struct El foo=1, bar=2>, #<struct El foo=2, bar=2>, #<struct El
foo=3, bar=5>, #<struct El foo=4, bar=5>, #<struct El foo=4, bar=8>,
#<struct El
foo=5, bar=9>, #<struct El foo=6, bar=7>, #<struct El foo=8, bar=2>,
#<struct El foo=8, bar=7>, #<struct El foo=8, bar=9>]
You can do this with sort_by but you have to create new instances (Arrays
in this case), which might or might not be acceptable depending on the
circumstances:
>> values.sort_by {|x| [x.foo, x.bar]}
=> [#<struct El foo=1, bar=2>, #<struct El foo=2, bar=2>, #<struct El
foo=3, bar=5>, #<struct El foo=4, bar=5>, #<struct El foo=4, bar=8>,
#<struct El
foo=5, bar=9>, #<struct El foo=6, bar=7>, #<struct El foo=8, bar=2>,
#<struct El foo=8, bar=7>, #<struct El foo=8, bar=9>]
I guess most real world cases can be covered by #sort_by.
Kind regards
robert