Ruby's Sort and Sort_by

Jul 8, 2018 - 6 min read
Ruby's Sort and Sort_by
Share

Whenever I hear the word sort in a programming context, I get instant flashbacks from my high-school days. It's like going back in time for a few seconds.

I can't seem to remember when and why I switched from "OMG" to "Oh! Cool!". But anyway, back to sorting, and sorting algorithms.

Just a quick recap of a few sorting algorithms I'm sure you've heard of.

Bubble sort

  1. Start at the beginning of the list.
  2. Compare each pair of elements in turn.
  3. If you need to swap them (i.e. left is bigger than the right), you swap them.
  4. You repeat the process until there are no more swaps.
Bubble Sort

One way to optimize bubble sort is to remove the last element from the list at each pass (because it's the largest value).

Merge sort

  1. You split your list of elements smaller and smaller lists until you get to a list of pairs.
  2. You sort each pair.
  3. You merge the pairs in order.
Merge Sort

Merge sort works better than bubble sort for larger lists.

Quicksort

  1. Choose a pivot.
  2. Put all the numbers smaller than or equal to the pivot to its left.
  3. Put all the numbers larger than the pivot to its right.
  4. For each sublist, choose a pivot and repeat the process until there is just one or no elements in each list.
Quick Sort

The way you choose the pivot can have a great impact on the operation's performance. Ideally, you want to choose the pivot in the middle of the list.

Working with the Ruby sort method

The Ruby sort method works by comparing elements of a collection using their <=> operator (more about that in a second), using the quicksort algorithm.

You can also pass it an optional block if you want to do some custom sorting. The block receives two parameters for you to specify how they should be compared. Let us see an example.

Sorting an array in descending order with sort

Here's how the sort method works when a block is passed to it.

[1, 2, 3].sort { |a, b| b <=> a } # => [3, 2, 1]

You can probably imagine how that block could easily fit any custom sorting preference.

You can learn more about arrays and see a few more examples on how to sort them in Learn How to Use Ruby Arrays in Less Than 10 Minutes.

The Ruby sorting operator (<=>)

Also called the spaceship operator, takes two parameters and returns one of three values.

  • 0 if the two parameters are equal.
  • -1 if the first parameter is less than the second parameter.
  • 1 if the first parameter is greater than the second parameter.

Note that when the result is 0, the order of the elements is unpredictable.

Sort vs. sort_by

The difference between sort and sort_by is in the parameters to the block.

sort receives two objects that you have to compare using their <=> operator.

sort_by receives just one object, and you have to call a method that will be used to map over the collection. If that sounds confusing, you're not alone. So let us see an example.

If you want to compare sort a two-dimensional array, by the size of each array in the list, you can call :size on the object the block receives.

matrix = [
  [1, 2, 3, 4, 5, 6],
  [1, 2, 3],
  [1, 2, 3, 4, 5],
  [:a, :b, :c, :d]
]

matrix.sort_by { |obj| obj.size }
# => [
  [1, 2, 3],
  [:a, :b, :c, :d],
  [1, 2, 3, 4, 5],
  [1, 2, 3, 4, 5, 6]
]

You can also do multiple sorting, where you sort using multiple values. If for example you want to sort by first and last names, you can do this.

Person = Struct.new(:fname, :lname)

p1 = Person.new("John", "Doe")
p2 = Person.new("Jane", "Doe")
p3 = Person.new("Marry", "Mackan")
p4 = Person.new("John", "Beck")

[p1, p2, p3, p4].sort_by { |p| [p.fname, p.lname] }
# => [
  #<struct  fname="Jane", lname="Doe">,
  #<struct  fname="John", lname="Beck">,
  #<struct  fname="John", lname="Doe">,
  #<struct  fname="Marry", lname="Mackan">
]

Without a block, sort_by returns an enumerator.

When you are dealing with larger collections, sort_by is usually what you want to use.

How to sort a string's characters alphabetically?

The chars method returns an array of characters. So you can just sort that array, and then join the elements back into a string.

"jhmaeb".chars.sort.join # => "abehjm"

And if you want to do a case insensitive sort, here's how.

"cdBghaZosa".chars.sort(&:casecmp).join # => "aaBcdghosZ"

If you're interested in learning more about how strings work in Ruby, check out How to Use Strings in Ruby.

How to Sort a Hash in Ruby

Often you want to sort a Hash just like you sort an Array. A hash is a list of key/value pairs. And it also includes the Enumerable module.

So let us say you want to sort a hash by its keys.

my_hash = {
  name: "John",
  age: 21,
  address: "Main Str. 11",
  email: "john@example.com"
}

my_hash.sort_by { |k, v| k }
# => [
  [:address, "Main Str. 11"],
  [:age, 21],
  [:email, "john@example.com"],
  [:name, "John"]
]

And if you want to sort it by value, it's just as easy my_hash.sort_by { |k, v| v }.

As you can see, the result is not a Hash. It's a two dimensional Array.

If you want to turn it into a hash, you can call to_h on it.

How to sort an array of hashes in ruby

If you've done any Rails development, I'm sure you've come across this one.

scores = [
  {name: "Superman", score: 745},
  {name: "Ironman", score: 9},
  {name: "Batman", score: 10}
]

scores.sort_by { |h | h[:name] }
# => [
  {:name=>"Batman", :score=>10},
  {:name=>"Ironman", :score=>9},
  {:name=>"Superman", :score=>745}
]
12 Project Ideas
Cezar Halmagean
Software development consultant with over a decade of experience in helping growing companies scale large Ruby on Rails applications. Has written about the process of building Ruby on Rails applications in RubyWeekly, SemaphoreCI, and Foundr.