Ruby, iOS, and Other Development

A place to share useful code snippets, ideas, and techniques

All code in posted articles shall be considered public domain unless otherwise noted.
Comments remain the property of their authors.

2006-10-23

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!

Labels: ,