Explanation of the sort() method
sort
is a basic array method available on the Array
object. As its name suggests, we can use it to sort or order the elements of arrays.
I will show some basic use cases of the method below.
Syntax
As sort
being a method, we can simply attach it to any array using the dot notation:
[element1, element2, element3, ..].sort();
sort
accepts an optional argument, a function called compare function and returns the sorted array.
Sorting without compare function
First, let’s have a look what happens if we don’t supply the compare function.
Assume we have an array of integers and want to display them in ascending order:
const numbers = [5, 9, 2, 7, 1, 4];
numbers.sort(); // [1, 2, 4, 5, 7, 9]
Everything seems to be good and shiny, until we add another number to the array:
const numbers = [5, 9, 2, 7, 1, 4, 13];
numbers.sort(); // [ 1, 13, 2, 4, 5, 7, 9 ]
Oops, something went wrong. 13 is obviously greater than 2, so it should come later.
When the compare function is not supplied, i.e. we call sort
without the argument, the elements in the array will be converted to strings and will be sorted according to their Unicode value. The Unicode value of 1
is 49 and both 1 and 13 starts with 1
, so both will come before 2
, whose Unicode value is 50.
Sorting numbers
Let’s have a look at the example above with the compare function in place.
This function accepts two arguments, let’s call them a
and b
in this order. a
and b
represents two elements in the array. The compare function must return a value and sorting will be based on this returned value.
If the returned value is negative, sort
will place a
before b
. If the returned value is positive, b
comes before a
. If this value is zero, a
and b
will remain where they were compared to each other, but will get sorted compared to other elements. (Note: sort
is not stable, which means that if there are multiple elements with the same value, their original order will not be guaranteed.)
Finally, let’s see how our numbers get sorted with the compare function in the conventional way:
const numbers = [5, 9, 2, 7, 1, 4, 13];
const sortedNumbers = numbers.sort((a, b) => a < b ? -1 : 1);
console.log(sortedNumbers); // [ 1, 2, 4, 5, 7, 9, 13 ]
In this case, we compare a
and b
as numbers. If a
is less than b
, we return a negative value (-1
) because we want to have the numbers in ascending order. Otherwise, we return 1
, i.e. b
will be placed before a
.
I wrote conventional because we don’t have to put -1
and 1
in the compare function. The main thing is that if we want the lower number to come first, we need to return a negative number, so the compare functions below will also return the correct sorted array:
const sortedNumbers2 = numbers.sort((a, b) => a < b ? -142 : 41261);
const sortedNumbers3 = numbers.sort((a, b) => a - b);
Sorting array of objects
What can we do if we have an array of objects?
Assume we have an array of friends, where each friend has a name and an age (after all, any friends have these properties). Let’s sort the array based on their age starting with the oldest friend:
const friends = [
{
name: 'Alice',
age: 37
},
{
name: 'Fred',
age: 34
},
{
name: 'Ron',
age: 40
},
{
name: 'Bob',
age: 42
}
];
const sortedFriends = friends.sort((a, b) => a.age < b.age ? 1 : -1);
Logging sortedFriends
to the console will return
[ { name: 'Bob', age: 42 },
{ name: 'Ron', age: 40 },
{ name: 'Alice', age: 37 },
{ name: 'Fred', age: 34 } ]
We assign a positive value (1
) if a.age < b.age
as we want the greater value (b.age
in this case) to come before the lower one.
Note that we didn’t return 0
in any of the examples above. We sorted numbers here and even if we had had equal numbers, it wouldn’t have mattered because changing the order of the two equal numbers (which happens by returning a value other than zero, as in the examples above) won’t change the end result. In case of 5 and 5 it doesn’t matter if the first number 5 is coming first or the second one.
Sorting objects with _.map
Sometimes the need arises for sorting objects.
What? How about the order of keys? It cannot be guaranteed!
That’s true, but luckily we have Lodash, which has great methods to the rescue.
Installing Lodash is easy, just follow the instructions on their website or click here.
Although I’m talking about large objects, I will show the example on a small one just to save time and space. The method works the same for larger data.
I’m using an object of Star Wars movies here and will sort them by their keys (you might disagree with the ratings):
const starWarsMovies = {
'The Last Jedi': {
director: 'Rian Johnson',
length: 152,
rating: 8.8
},
'A New Hope': {
director: 'George Lucas',
length: 121,
rating: 8.2
},
'The Force Awakens': {
director: 'J.J. Abrams',
length: 136,
rating: 8.4
},
'The Empire Strikes Back': {
director: 'Irvin Kershner',
length: 124,
rating: 8.8
}
};
I want to sort the movies according to their ratings in descending order, i.e. the best movie comes first. In case two movies have the same rating, I want to order them by their title in alphabetical (ascending) order.
I will use the orderBy
method, which works on both objects and arrays. The method (similarly to JavaScript’s sort
) returns an array of the sorted elements.
Use _.map first
If we use orderBy
straight on starWarsMovies
, we’ll get the movies in order but will lose their titles, because the method sorts values and unfortunately, titles were the keys of the movies object. We can agree that only hard-core fans can recognize from the array below which movie is which:
[ { director: 'Rian Johnson', length: 152, rating: 8.8 },
{ director: 'Irvin Kershner', length: 124, rating: 8.8 },
{ director: 'J.J. Abrams', length: 136, rating: 8.4 },
{ director: 'George Lucas', length: 121, rating: 8.2 } ]
So first we need to “save” the titles of the movies (aka the keys of starWarsMovies
). What we can do is to map on starWarsMovies
and create a new object, where we keep the values of each object and add an additional property of titles to them.
We can use _.map
here, which also works on both objects and arrays:
const moviesArr = _.map(starWarsMovies, (movie, title) => {
return _.assign({ title }, movie);
});
Let’s break it down. _.map
is very similar to native JavaScript’s map
. We have to supply the collection we want to map on in the first argument, in this case it’s going to be starWarsMovies
.
The second argument is the callback function, which is also necessary for the native method. The first argument of the callback function is the actual movie object the iteration is at (movie
). The second parameter (title
) is the key of the current object. (In case of arrays, similarly to the native map
, the second parameter will be the index of the element.)
For each movie
object we are creating a new object with _.assign
, which adds the title of the movie (which is the key of the current object) as the descriptive title
property to the rest of the movie
. As a result, we will get an array of object (moviesArr
) returned from _.map
, where each object has four properties instead of the original three (director
, length
, rating
and the new title
property).
We can sort it now
It’s all good, we can sort it now. Using the orderBy
method we can write something like this:
const sortedMovies = _.orderBy(moviesArr, ['rating', 'title'], ['desc', 'asc']);
As with _.map
the first argument is the collection, moviesArr
, which we want to order. The “By” part from orderBy
comes second in a form of an array of strings. The strings (rating
, title
) are properties of the objects in moviesArr
and it says that we want to order the movie elements by rating
first (first element of the argument array), and then by title
if multiple objects exist with the same rating.
Finally, we define how we want to order the objects. They will be sorted by rating
in descending order (first element of the third array, desc
) and when we need to sort by title
, we want it in ascending order (asc
).
We save the returned result, which is an array, in a variable called sortedMovies
.
If we run the function in Node, we’ll get the following result:
[ { title: 'The Empire Strikes Back',
director: 'Irvin Kershner',
length: 124,
rating: 8.8 },
{ title: 'The Last Jedi',
director: 'Rian Johnson',
length: 152,
rating: 8.8 },
{ title: 'The Force Awakens',
director: 'J.J. Abrams',
length: 136,
rating: 8.4 },
{ title: 'A New Hope',
director: 'George Lucas',
length: 121,
rating: 8.2 } ]
Conclusion
Sorting is a frequently used feature and a must have in a JavaScript toolkit. I showed some very simple examples on how to use sort
as well as a slightly more complicated one in Lodash.
Note that the technique applied in Lodash (mapping and sorting) can be also used on arrays of objects in native JavaScript (with map
and sort
) and it’s recommended if the objects have many properties. I didn’t show any example for this case as it’s almost identical to the one solved with Lodash.
Enjoy coding and see you next time!