Sorting an array in Ruby is easy!
You don’t need to write any fancy algorithms to get the result you want.
What’s needed, then?
Understanding Ruby’s built-in sorting methods.
These:
- sort
- sort_by
- sort!
How do these methods work & why are they different?
That’s what you’ll discover in this article.
You’ll learn the different ways of sorting an array, starting with the sort
method, then taking a look at sort_by
for advanced sorting (by multiple values) & more.
Let’s do this!
Learn to Use the Sort & Sort! Ruby Methods
The most basic form of sorting is provided by the Ruby sort method, which is defined by 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.
An array of sorted elements!
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
With the sort_by
method you can do more advanced & interesting sorting.
You’ll be able to:
- Sort by string length
- Sort by string contents
- Sort by whether a number is even or odd
You can do this with the sort_by
method & a Ruby block.
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 words that start with a capital letter & 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("calendar Cat tap Lamp") # "Cat Lamp calendar tap"
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"]
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).
In general, I prefer the sort_by
method because the intention is more clear, it’s easier to read & it is also a bit faster.
Sort in Reverse Order
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"]
Not fancy, but it works! 🙂
Alphanumeric Sorting
Let’s say you want to numerically 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.
Example:
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
You may want to sort something by multiple attributes, meaning that you first sort by date (for example), but because you have multiple things with the same date then you have a tie.
To break the tie you can use a secondary attribute.
Example:
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 inside the sort_by
block.
Where you set the primary sorting attribute as the first element of the array (event.date
) & then the secondary tie-breaker attribute (event.name
).
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
[responsive_video type=’youtube’ hide_related=’1′ hide_logo=’0′ hide_controls=’0′ hide_title=’0′ hide_fullscreen=’0′ autoplay=’0′]https://www.youtube.com/watch?v=z9aa3f7QETI[/responsive_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.
Here’s what you should remember:
- You can use the
sort
method on an array, hash, or another Enumerable object & you’ll get the default sorting behavior (sort based on<=>
operator) - You can use
sort
with a block, and two block arguments, to define how one object is different than another (block should return 1, 0, or -1) - You can use
sort_by
with a block, and one argument, to define one attribute for each object which is going to be used as the basis for sorting (array length, object attribute, index, etc.). The block should return an integer value which determines the position of the object in the sorted array.
Don’t forget to share this post so more people can learn. 🙂
The Alphanumeric sorting input array (music) does not match the sorted array data.
Sorry! I fixed it now 🙂
Thanks for these great articles. Your site is also very neat.
Thanks for reading 🙂
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.
Thank you David!
I fixed that 🙂
Your quicksort implementation will not deal properly with arrays containing duplicates, as the pivot element (number) is only included once.
You are right! I updated the code to make it work with duplicates 🙂
Great! Thanks 🙂
Thanks for reading!
Great and helpful article! Keep up the good work !! 🙂
Thank you 🙂
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.Thanks for your comment 🙂