DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’»

Cover image for Sorting an array in JavaScript, a utility perspective!
Antonio Villagra De La Cruz
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)
Enter fullscreen mode Exit fullscreen mode

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]
Enter fullscreen mode Exit fullscreen mode

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]
Enter fullscreen mode Exit fullscreen mode

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`.
Enter fullscreen mode Exit fullscreen mode

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

function numerically (a, b) {
  return a - b;
}
Enter fullscreen mode Exit fullscreen mode

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!
}
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

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;
  }
}
Enter fullscreen mode Exit fullscreen mode

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")
  • Append a - sign in the key (for example: sort(alphabetically.by("-name"))
  • 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)
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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:

GitHub logo AntonioVdlC / sort

πŸ” - Custom compare functions for sorting arrays

sort

version issues downloads license

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";
Enter fullscreen mode Exit fullscreen mode

- or -

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

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]
Enter fullscreen mode Exit fullscreen mode

By default, sorting is doing in an ascending fashion. All…

Top comments (0)

🌚 Life is too short to browse without dark mode