DEV Community

Antonio Villagra De La Cruz

Posted on • Updated on • Originally published at antoniovdlc.me

Sorting an array in JavaScript, a utility perspective!

If you have written some JavaScript and manipulate slightly complex data, you have had to write some code like this to sort an array of objects:

const data = [
{ name: "Alice", age: 22 },
{ name: "Bob", age: 32 },
{ name: "Carl", age: 63 },
{ name: "Clara", age: 28 },
...
];

data.sort(function(a, b) {
if (a.name < b.name) {
return -1;
}

if (a.name > b.name) {
return 1;
}

return 0;
})

// Or, as a one-liner:
data.sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0)

While this is perfectly fine for one-off sorting of shallow objects, it can get a bit more complex and repetitive when having to sort based on nested fields.

Something else you might have tripped on while using the native .sort() on arrays, is the following behaviour:

const array = [1, 2, 3, 10, 23]
console.log(array.sort())
// [1, 10, 2, 23, 3]

Indeed, by default, the comparison function used by .sort() treats each element as a string! To make the above example work, you need to pass a custom comparison function such as the following one-liner:

const array = [1, 23, 3, 10, 2]
console.log(array.sort((a, b) => a - b))
// [1, 2, 3, 10, 23]

As sorting is a common operation on arrays, a more scalable and less error-prone strategy would be to define common compare functions. Let's build said compare functions!

First, let's look at the API we would like to end up with:

const array = [1, 23, 3, 10, 2]
array.sort(numerically)
// Should be equivalent to:
array.sort((a, b) => a - b)

array.sort(numerically.desc)
// Should be equivalent to:
array.sort((a, b) => b - a)
// For completeness, we can also expose `numerically.asc`.

To achieve the above API, we can define numerically as follows:

function numerically (a, b) {
return a - b;
}

As in JavaScript, (almost) everything is an object, we can then add a desc and an asc field to the numerically function as follows:

numerically.desc = function(a, b) {
return b - a;
}

numerically.asc = function(a, b) {
return numerically(a, b); // This works because we sort from lower to higher by default!
}

Now that we have defined compare functions to work on arrays holding primitives values, let's generalise it to arrays of objects:

const data = [
{ name: "Alice", age: 22 },
{ name: "Bob", age: 32 },
{ name: "Carl", age: 63 },
{ name: "Clara", age: 28 },
...
];

data.sort(alphabetically.by("name"))
// Should be equivalent to:
data.sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0)

To achieve that, let's create a small utility function that will help us retrieve the value of an object based on a key path:

function getValueByKey(obj, key) {
return String(key)
.split(".")
.reduce((acc, cur) => acc?.[cur] ?? null, obj);
}

With the code above, we can do deep object look-ups!

With that in hand, let's add the following to our example alphabetically sort function:

function alphabetically (a, b) { ... }

alphabetically.desc = function(a, b) { ... }
alphabetically.asc = function(a, b) { ...}

alphabetically.by = function(key) {
return function(a, b) {
const aVal = getValueByKey(a, key);
const bVal = getValueByKey(b, key);

return a < b ? -1 : a > b ? 1 : 0;
}
}

Alright, this works great for ascending order sorting, but how could we implement descending order? There are different ways of solving this:

• Pass another argument that can have either "desc" or"asc" values (defaults to "asc")
• Add .desc() and .asc() functions to our new function .by()

Either designs are fine, but to stay consistent with our previous utility function, we will be adding the ordering feature as follow:

data.sort(alphabetically.by("name").desc)

All implemented, it looks like:

function alphabetically (a, b, direction = 1) {
if (a < b) {
return -1 * direction;
}

if (a > b) {
return 1 * direction;
}

return 0;
}

alphabetically.asc = (a, b) => alphabetically(a, b, 1);
alphabetically.desc = (a, b) => alphabetically(a, b, -1);

alphabetically.by = function(key) {
function compareBy(a, b, direction = 1) {
const aVal = getValueByKey(a, key);
const bVal = getValueByKey(b, key);

return aVal < bVal ? -1 * direction : aVal > bVal ? 1 * direction : 0;
}

compareBy.asc = (a, b) => compareBy(a, b, 1);
compareBy.desc = (a, b) => compareBy(a, b, -1);

return compareBy;
}

I found this exercise particularly interesting, and decided to build a library with some of the ideas discussed in this post. You can have a look at it here:

sort

Custom compare functions for sorting arrays.

Installation

This package is distributed via npm:

npm install @antoniovdlc/sort

Motivation

Sorting arrays is a common operation in JavaScript, so this library provides some common custom compare functions to have a more declarative way of sorting arrays.

Usage

You can use this library either as an ES module or a CommonJS package:

import { alphabetically, chronologically, numerically } from "@antoniovdlc/sort";

- or -

const { alphabetically, chronologically, numerically } = require("@antoniovdlc/sort");

Examples

All compare functions can be used out of the box for sorting as follows:

import { numerically } from "@antoniovdlc/sort";
const arr = [1, 2, 2, 23, 30, 4];
arr.sort(numerically); // [1, 2, 2, 4, 23, 30]

By default, sorting is doing in an ascending fashion. Allβ¦