This content originally appeared on DEV Community and was authored by Hamdan Khan
Have you ever stumbled accross the need for sorting your data not on the basis of a single but multiple attributes?
Well let’s go through an example:
Suppose we have an array of data: scores
, which holds the records of let’s say results of people who took part in a competiton. The requirement is to prepare the Final Rankings of the participants i.e. sort the data by scores in descending order.
A simpler un-sorted version of the data, for the sake of explanation, would look somewhat like this:
scores = [
{score: 90, updatedAt: "2024-06-01", name: "John"},
{score: 90, updatedAt: "2024-06-01", name: "Bob"},
{score: 80, updatedAt: "2024-06-02", name: "Charlie"},
{score: 90, updatedAt: "2024-05-30", name: "Dave"},
{score: 80, updatedAt: "2024-05-30", name: "Eve"},
]
Sorting this data seems pretty easy. Array.prototype.sort()
can be used to sort the array in descending order of scores.
scores.sort((a,b) => b.score - a.score);
// after sorting
[
{score: 90, updatedAt: "2024-06-01", name: "John"},
{score: 90, updatedAt: "2024-06-01", name: "Bob"},
{score: 90, updatedAt: "2024-05-30", name: "Dave"},
{score: 80, updatedAt: "2024-06-02", name: "Charlie"},
{score: 80, updatedAt: "2024-05-30", name: "Eve"},
]
- John, bob and Dave took the joint first place.
- Charlie and Eve tied for the second place.
Multi-level sorting
If we take a deeper look at the scores data, we see that people participated in the competition at different dates. For example, Bob participated on 2024-06-01 whereas Dave participated on 2024-05-30.
For participants with the same score, isn’t that a bit unfair to those who participated earlier to be placed below the participants who participated later?
So how do we sort the array now, such that:
- The data is sorted in descending order of scores.
- For records with same scores, data is sorted in the ascending order of participation dates (earlier -> later).
Would sorting the data twice based on the attributes get us our desired result?
scores.sort((a,b) => b.score - a.score);
scores.sort((a,b) => a.updatedAt - b.updatedAt);
// after sorting
[
{score: 90, updatedAt: "2024-05-30", name: "Dave"},
{score: 80, updatedAt: "2024-05-30", name: "Eve"},
{score: 90, updatedAt: "2024-06-01", name: "John"},
{score: 90, updatedAt: "2024-06-01", name: "Bob"},
{score: 80, updatedAt: "2024-06-02", name: "Charlie"},
]
Nope, the data would be sorted to the latest sorting order instead i.e. ascending order of participation dates. This looks nothing like a Final Ranking.
Solution
The solution is to sort the data based on multiple conditions. Or we can say sort it on multiple levels:
scores.sort((a,b) => {
// if scores are not same, sort based on the desc order of score
if (a.score !== b.score) {
return b.score - a.score;
}
// otherwise sort based on the asc order of participation date
return a.updatedAt - b.updatedAt;
});
// after sorting
[
{score: 90, updatedAt: "2024-05-30", name: "Dave"},
{score: 90, updatedAt: "2024-06-01", name: "John"},
{score: 90, updatedAt: "2024-06-01", name: "Bob"},
{score: 80, updatedAt: "2024-05-30", name: "Eve"},
{score: 80, updatedAt: "2024-06-02", name: "Charlie"},
]
Now this looks more like a fair Final Ranking.
The sorting conditions can be simplified further using the OR operator:
scores.sort((a,b) => (b.score - a.score) || (a.updatedAt - b.updatedAt));
This content originally appeared on DEV Community and was authored by Hamdan Khan