Sorting data in ascending and descending order

Sorting Data

I was looking at a React tutorial the other day on how to sort tables by column. Midway through the tutorial I couldn’t resist doing a bit of experimentation to see I how I might approach the task and just thought I would share what I learnt along the way.

The following is a look at one part of that, which is Array.sort

Array.sort()

To start with I want to look at the typical array sort routine — usually something like this. Note I am aware we could just use the default `Array.sort()` method, but thought best to be explicit.

``````const comparator = (a, b) => {
if (a < b) return -1 // shift to the left
if (a > b) return 1 // shift to the right
return 0 // stay where you are
}

const letters = ['c', 'f', 'a', 'd']
const numbers = [5, 3, 1, 7]

letters.sort(comparator)
// ['a', 'c', 'd', 'f']
numbers.sort(comparator)
// [1, 3, 5, 7]
``````

The above works fine for same case letters, but doesn’t work so well if we mix upper and lowercases. It also gives unexpected results for a sequence of numbers in string format.

``````const letters = ['Z', 'a', 'd', 'F']
const numbers = ['101', '5', '3', '210']
letters.sort(comparator)
// ['F', 'Z', 'a', 'd']
numbers.sort(comparator)
// ['101', '210', '3', '5']
``````

The reason for the above result is due to the compare operators (< >) evaluating each character to its unicode value. For instance uppercase letters have lower values than lowercase letters e.g.

``````'a'.charCodeAt(0) // 97
'A'.charCodeAt(0) // 65
``````

So one solution to that might be to amend the comparator accordingly

``````const comparator = (a, b) => {
const toLowerA = a.toLowerCase() // convert to lowercase
const toLowerB = b.toLowerCase() // same here

if (toLowerA < toLowerB) return -1
if (toLowerA > toLowerB) return 1
return 0
}

const letters = ['Z', 'a', 'd', 'F']
letters.sort(comparator)
// ['a', 'd', 'F', 'Z']
``````

This has solved the problem with letters, but what about numbers? Fortunately there is a one stop solution in the form of localeCompare

``````'b'.localeCompare('a') // 1
'a'.localeCompare('b') // -1
``````

LocaleCompare provides us with the option to handle numbers in string format as well.

``````'101'.localeCompare('2', undefined, { numeric: true }) // 1
'b'.localeCompare('a', undefined, { numeric: true }) // 1
``````

We can now re-write the comparator accordingly.

``````const comparator = (a, b) => {
const stringA = a.toString()
const stringB = b.toString()

return stringA.localeCompare(stringB, undefined, { numeric: true })
}

const letters = ['c', 'f', 'a', 'd']
const numbers = [101, 5, 3, 210]

letters.sort(comparator)
// ['a', 'c', 'd', 'f']
numbers.sort(comparator)
// [3, 5, 101, 210]
``````

Reverse order

To finish this off and for a bit of fun I want to throw in a functional method that can help us to reverse the order called flip.

``````const flip = (fn) => (a, b) => fn(b, a)
``````

It takes a binary callback function, a function with two parameters, and returns a function that calls that given callback with the arguments in reverse order. We can use it like this.

``````const comparator = (a, b) => {
... same as last example
}

const reverseComparator = flip(comparator)

const letters = ['c', 'f', 'a', 'd']
const numbers = [101, 5, 3, 210]

letters.sort(reverseComparator)
// ['f', 'd', 'c', 'a']
numbers.sort(reverseComparator)
// [210, 101, 5, 3]
``````

So there you go, hopefully that was short and to the point.

Note: There is another option that follows the localeCompare approach/pattern that is also worth considering, called Intl.collator. I will leave that to you

2 Likes

I like it. Dont know if i’d have bothered creating flip (`letters.sort(comparator).reverse()` accomplishes the same, a bit more directly/legibly?), but a good exploration of the subject.

Makes me glad PHP has all of those sorting functions predefined… now if only we could get a spaceship operator in Javascript…

1 Like

You’ve got a point there, nonetheless I think flip is a nice little function to know about.

1 Like

To reverse the sort order just multiply the return value of the comparator function by -1.
Or add another parameter into the comparator function 1 for normal sort and -1 for reverse sort

``````const comparator = (a, b, c) => {
if (a < b) return -1 * c
if (a > b) return 1 * c
return 0 // stay where you are
}
``````

I had thought about that, but Sort will only pass 2 parameters (a and b) to the comparator function. You could fix a global variable, but thats a last resort.

You can functionally Multiply by -1 by defining flip to be `flip = (fn) => (a,b) => fn(a,b) * -1`, but that doesnt really shorten anything.

If you want to take that approach you could go with currying.

``````const comparator = _curry3((c, a, b) => {
if (a < b) return -1 * c
if (a > b) return 1 * c
return 0
})
``````

or a more manual approach

``````const comparator = (c = 1) =>
(a, b) => {
if (a < b) return -1 * c
if (a > b) return 1 * c
return 0
})
``````
``````[101, 5, 3, 210].sort(comparator(-1)) // [210, 101, 5, 3]
``````

This topic was automatically closed 91 days after the last reply. New replies are no longer allowed.