Using sort_by instead of sort
Q: What's wrong with this code?
some_collection.sort { |a,b| a.foo <=> b.foo }
A: Efficiency. That block will be called n log n times (expected number of comparison operations involved in a quicksort), which means that the foo method will be called twice as many times. It also isn't terribly readable, since it looks like the block is there to perform some important function, when really it's just defining on what attribute the element are being sorted. The way to avoid this is:
some_collection.sort_by { |a| a.foo }
In Rails, it is also possible to use the idomatic
some_collection.sort_by &:foo
...though this is somewhat less clear and should probably be avoided. The sort_by method calls the block once on each element of the collection and uses it as a surrogate sort value. Instead of foo being called 2n log n times, it is called n times, as is the block. It is also clear that the foo attribute is being used as the value for comparison.
Q: Okay, but what if it's more complicated like:
some_collection.sort do |a,b| val = a.foo <=> b.foo val = a.bar <=> b.bar if val == 0 val = a.baz <=> b.baz if val == 0 val end
A: That basically says "sort by foo, then bar, then baz." You still can and should use sort_by:
some_collection.sort_by { |a| [ a.foo, a.bar, a.baz ] }
Arrays are compared element-by-element, with the first element being most significant and subsequent elements increasingly insignificant. By providing an array as a surrogate object, the sort precedence works as intended. Enjoy!
3 Comments:
At 10/23/2006 01:48:00 PM, Anonymous said…
I would like to see the same thing for the max and min enumerable methods. I suppose we can do sort_by and call last/first, but a max_by/min_by type method would be more convenient. I wonder if this is in the works for 1.9?
At 11/13/2006 06:15:00 AM, Martin DeMello said…
I find this a useful complement to sort_by
At 11/09/2009 01:27:00 PM, Anonymous said…
Excellent. Thanks.
Post a Comment
<< Home