The Difference Between .sort and .sort_by in Ruby

The Difference Between .sort and .sort_by in Ruby

Image for postSource: https://i1.wp.com/altereddimensions.net/main/wp-content/uploads/2016/09/image-36.png

I just completed my second week of the Software Engineering Immersive at Flatiron School, and I?m going to unpack the difference between .sort and .sort_by in Ruby. I first ran into .sort_by when working on the final lab in the pre-work for my program. The lab was called ?Alphabetize in Esperanto,? referring to the international auxiliary language known as Esperanto. The challenge? To write a method that accepts an array of strings and sorts them alphabetically according to the Esperanto alphabet. I solved the lab using .sort_by, but even so, I was still unsure of how .sort_by works ?under the hood,? and how it?s different from .sort. So, I decided to dive in. Here?s what I learned.

How .sort works:

.sort is a Ruby enumerator that compares two elements in an array at a time. It can be called with or without a block, but if called with a block, the block requires two arguments, which represent the two things being compared. When called without a block, .sort implicitly relies on the comparison method <=> operator (usually called the spaceship operator). The spaceship operator enacts a cascading if/elsif/else statement that returns 0 if the two operands are equal to one another, -1 if the first operand is less than the second, and 1 if the first operand is greater than the second. These return values shuffle the elements up and down the list until they are in the specified order.

How .sort_by works:

In contrast to .sort, .sort_by must be used with a block, and the block only takes one argument. When using .sort_by, you typically want to manipulate the argument in some way in order to make it sortable. For example, if you wanted to sort an array of strings according to length, you would call:

string_array.sort_by { |string| string.length }

.sort_by works by creating what you can think of as an invisible hash. When called on an array, it calculates a set of numerical keys (known as ?sort keys?), and assigns each element in the array to one of those sort keys. Then, the keys are sorted, and mapped back onto the original values. The return value is the sorted array.

Given this distinction, let?s break down the solution to ?Alphabetize in Esperanto.?

For starters, let?s assume that the array we?d like to alphabetize according to the Esperanto alphabet is as follows:

sentences_array = [“mi amas programadon”, “ruby estas mia plej ?atata lingvo”, “ni ordigu ?i tiujn frazojn”, “laboru fervore ludu fervore”, “?u vi ?atas kodon”]

We?ll also need the below constant:

ESPERANTO_ALPHABET = “abc?defg?h?ij?klmnoprs?tu?vz”

And this is the method I wrote, which I will unpack step-by-step:

Here?s how it works:

Step 1.

sentences_array.sort_by do |sentence|

With this line, I?m calling the .sort_by method on sentences_array. Each sentence in the array will get passed into the block as ?sentence.? So, the first time through, ?sentence? will be ?mi amas programadon? (ain?t it the truth?).

Step 2.

sentence.split(“”).map do |character| ESPERANTO_ALPHABET.index(character)end

Here, I?m turning the sentence into an array, where each element of the array is a letter in the sentence. Then, I?m calling .map on that array, which means I?ll be mapping a new value, according to the code in the block, onto each element of the array of letters. So, the array of letters will have the same number of elements as it originally did, but each element will be transformed.

For example, if ?sentence? is ?mi amas programadon,? it gets turned into the array [?m?, ?i?, ? ?, ?a?, ?m?, ?a?, ?s?, ? ?, ?p?, ?r?, ?o?, ?g?, ?r?, ?a?, ?m?, ?a?, ?d?, ?o?, ?n?]. Each element in that array is going to be passed into the block as ?character.?

The first element, which we are calling ?character,? is ?m.? The block will call:

ESPERANTO_ALPHABET.index(“m”)

.index(), when called on a string, works by revealing which letter in the string (numerically) corresponds to whatever gets passed in to .index() as the argument (remember that strings, just like arrays, are 0-indexed). In this case, ?m? corresponds to index 16 in ESPERANTO_ALPHABET, so 16 replaces ?m? in the array of letters. By the end of the loop, our original array will look like this:

[“16”, “11”, “nil”, “0”, “16”, “0”, “21”, “nil”, “19”, “20”, “18”, “7”, “20”, “0”, “16”, “0”, “4”, “18”, “17”]

Steps 1 and 2 will happen for each and every sentence in the original sentences_array.

Step 3:

Now, it?s time for the .sort_by magic. We have five sentences (currently represented as arrays with integers as elements), so imagine that .sort_by generates five numerical keys: 1, 2, 3, 4, and 5. It assigns the first sentence (remember, it?s currently represented by an array containing a bunch of integers) to the first key. Then comes the second sentence (also represented by an array containing a bunch of integers). If the first integer in the second sentence is less than the the first integer in the first sentence, the second sentence gets assigned to the 1 key and the first sentence gets bumped down to the 2 key. And so on and so forth. By the end, you can envision a hash that looks like this:

sorted_hash = { “1” => [“3”, “24”, “nil”, “26”, “11”, “22”, “0”, “23”, “0”, “21”, “nil”, “14”, “18”, “4”, “18”, “17”], “2” => [“15”, “0”, “1”, “18”, “20”, “24”, “nil”, “6”, “5”, “20”, “26”, “18”, “20”, “5”, “nil”, “15”, “24”, “4”, “24”, “nil”, “6”, “5”, “20”, “26”, “18”, “20”, “5”], “3” => [“16”, “11”, “nil”, “0”, “16”, “0”, “21”, “nil”, “19”, “20”, “18”, “7”, “20”, “0”, “16”, “0”, “4”, “18”, “17”], “4” => [“17”, “11”, “nil”, “18”, “20”, “4”, “11”, “7”, “24”, “nil”, “3”, “11”, “nil”, “23”, “11”, “24”, “12”, “17”, “nil”, “6”, “20”, “0”, “27”, “18”, “12”, “17”], “5” => [“20”, “24”, “1”, “nil”, “nil”, “5”, “21”, “23”, “0”, “21”, “nil”, “16”, “11”, “0”, “19”, “15”, “5”, “12”, “nil”, “22”, “0”, “23”, “0”, “23”, “0”, “nil”, “15”, “11”, “17”, “7”, “26”, “18”]}

Next, the .sort_by method maps those sort keys onto the original values:

sorted_hash = { “1” => “?u vi ?atas kodon”, “2” => “laboru fervore ludu fervore”, “3” => “mi amas programadon”, “4” => “ni ordigu ?i tiujn frazojn”, “5” => “ruby estas mia plej ?atata lingvo”}

Finally, the .sort_by method will return an array with the elements in order:

[“?u vi ?atas kodon”, “laboru fervore ludu fervore”, “mi amas programadon”, “ni ordigu ?i tiujn frazojn”, “ruby estas mia plej ?atata lingvo”]

And that?s it! No spaceships with .sort_by; instead, it?s all about the sort keys.

19