RubyGuides
Share this post!

How to Sort Arrays & Hashes in Ruby (Examples Included)

How many ways are there to sort an array in Ruby?

More than you think…

Even though Array has only two sorting methods (sort & sort_by) these methods can take a block, which allows you to sort in many different ways.

I want to share with you a few examples in this post!

You’ll also learn how to implement your own sorting method using the quick-sort algorithm.

Basic Sorting Method

The most basic form of sorting is provided by the Ruby sort method, which is defined in the Enumerable module.

Let’s see an example:

numbers = [5,3,2,1]

numbers.sort

# [1,2,3,5]

Notice that sort will return a new array with the results.

It’s also possible to sort “in-place” using the sort! method.

This means that the original array will change instead of creating a new one, which can be good for performance.

Customized Sorting With sort_by

Let’s discover how to do more advanced sorting.

You’ll be able to:

  • Sort by string length
  • Sort by string contents
  • Sort by wether a number is even or odd

You can do this with the sort_by method.

For example:

strings = %w(foo test blog a)

strings.sort_by(&:length)

# ["a", "foo", "test", "blog"]

How does this work?

Well, the sort_by method expects a numerical value, that’s why length works.

If you understand this, then you can use this method to do cool things, like sorting a word starting with a capital letter first & leaving everything else in place.

Like this:

def sort_by_capital_word(text)
  text
    .split
    .sort_by { |w| w[0].match?(/[A-Z]/) ? 0 : 1 }
    .join(" ")
end

sort_by_capital_word("a b c D")

It is also possible to do custom sorting using the regular sort method with a block.

Here’s an example:

strings = %w(foo test blog a)

strings.sort { |a,b| a.length <=> b.length }

# ["a", "foo", "test", "blog"]

But in general & prefer the sort_by method because the intention is more clear, it’s easier to read & it is also a bit faster.

Note: This <=> symbol is called “the spaceship operator” & it’s a method you can implement in your class. It should return 1 (greater than), 0 (equal) or -1 (less than).

Reverse Sort

What about sorting in reverse?

You could use the reverse method after sorting, or you can use a block & put a minus sign in front of the thing you are sorting.

Let me show you an example:

strings = %w(foo test blog a)

strings.sort_by { |str| -str.length }

# ["blog", "test", "foo", "a"]

Alphanumeric Sorting

Let’s say you want to sort a list of strings that contain numbers.

Like this:

music = %w(21.mp3 10.mp3 5.mp3 40.mp3)

By default you will not get this list sorted like you want:

music.sort

# ["10.mp3", "21.mp3", "40.mp3", "5.mp3"]

But you can fix this using sort_by:

music.sort_by { |s| s.scan(/\d+/).first.to_i }

# ["5.mp3", "10.mp3", "21.mp3", "40.mp3"]

I used a regular expression (\d+) to match the numbers, then get the first number (first) & convert it to an integer object (to_i).

How to Sort Hashes in Ruby

You are not limited to sorting arrays, you can also sort a hash.

Example:

hash = {coconut: 200, orange: 50, bacon: 100}

hash.sort_by(&:last)

# [[:orange, 50], [:bacon, 100], [:coconut, 200]]

This will sort by value, but notice something interesting here, what you get back is not a hash.

You get a multi-dimensional array when sorting a hash.

To turn this back into a hash you can use the Array#to_h method.

Sorting By Multiple Values

If you want to sort by date, and then sort by name, you can do it like this:

Event = Struct.new(:name, :date)
events = []

events << Event.new("book sale", Time.now)
events << Event.new("course sale", Time.now)
events << Event.new("new subscriber", Time.now)
events << Event.new("course sale", Time.now + 1.day)

events.sort_by { |event| [event.date, event.name] }

The key here is the array, where you set the main sorting criteria first & then the rest separated by commas.

QuickSort Implementation

Just for fun let’s implement our own sorting method. This is going to be slower than the built-in sort methods, but it’s still an interesting exercise if you like computer science.

def quick_sort(list)
  return [] if list.empty?

  groups = list.group_by { |n| n <=> list.first }

  less_than    = groups[-1] || []
  first        = groups[0]  || []
  greater_than = groups[1]  || []

  quick_sort(less_than) + first + quick_sort(greater_than)
end

p quick_sort [3, 7, 2, 1, 8, 12]

# [1, 2, 3, 7, 8, 12]

The idea of quick sort is to pick one number at random then divide the list we are sorting into two groups.

One group is the numbers less than the chosen number & the other group is the numbers bigger than the chosen number.

Then we just repeat this operation until the list is sorted.

Benchmarks

Let’s see how all these sorting methods compare to each other in terms of performance.

Ruby 2.4.0:

  sort!:                1405.8 i/s
  sort:                 1377.6 i/s - same-ish: difference falls within error
  sort_by reverse:      196.6  i/s - 7.15x  slower
  sort_by:              183.7  i/s - 7.65x  slower
  sort_by minus:        172.3  i/s - 8.16x  slower
  sort with block:      164.1  i/s - 8.57x  slower

As you can see the regular sort method is a lot faster than sort_by, but it’s not as flexible unless you use a block.

Video

Summary

You have learned how to use the sort & the sort_by methods to sort your arrays & hashes in different ways. You have also learned about the performance differences & how to implement the quicksort algorithm.

To remember:

  • You can use the sort method by itself for the default sorting behaviour (sort based on == operator)
  • You can use sort with a block, plus two block arguments, to define how one object is different than another (return 1, 0, -1)
  • You can use sort_by with a block, and one argument, to define one characteristic for each object which is going to be used as the basis for sorting (array length, object attribute, index, etc.). Must return an integer value which determines the position of the object.

Don’t forget to share this post so more people can learn 🙂

14 comments
Anand says a couple of years ago

The Alphanumeric sorting input array (music) does not match the sorted array data.

    Jesus Castello says a couple of years ago

    Sorry! I fixed it now 🙂

Demba says a couple of years ago

Thanks for these great articles. Your site is also very neat.

    Jesus Castello says a couple of years ago

    Thanks for reading 🙂

David Jenkins says a couple of years ago

Hi, thanks for publishing this great guide. Just wanted to alert you to a typo:

In the Alphanumeric Sorting section, your array starts like this:

music = %w(21.mp3 50.mp3 1.mp3 40.mp3)

but then the results if music.sort are displayed as this:

[“10.mp3”, “21.mp3”, “40.mp3”, “5.mp3”]

i.e., 1.mp3 changed to 10.mp3 and 50.mp3 changed to 5.mp3

Feel free to delete this comment if you want.

    Jesus Castello says a couple of years ago

    Thank you David!

    I fixed that 🙂

Klaus says a couple of years ago

Your quicksort implementation will not deal properly with arrays containing duplicates, as the pivot element (number) is only included once.

    Jesus Castello says a couple of years ago

    You are right! I updated the code to make it work with duplicates 🙂

Mr. Ahmed says a couple of years ago

Great! Thanks 🙂

    Jesus Castello says a couple of years ago

    Thanks for reading!

Ankur says a couple of years ago

Great and helpful article! Keep up the good work !! 🙂

    Jesus Castello says a couple of years ago

    Thank you 🙂

Vedran says a couple of years ago

No need for “s.scan(/\d+/).first.to_i” if the number is at the beginning of string, just simple “s.to_i” would do the job.

    Jesus Castello says a couple of years ago

    Thanks for your comment 🙂

Comments are closed