I was recently reading a post by Jaime González García which featured the following mind-bending proposition:
Optional. Specifies a function that defines the sort order. If omitted, the array is sorted according to each character's Unicode code point value, according to the string conversion of each element.
We want to use the
sort function to introduce some LINQ-ish ordering goodness. Sort of. See what I did there?
Before we get going it's worth saying that LINQ's
sort are not the same thing.
sort actually changes the order of the array. However,
OrderBy returns an
IOrderedEnumerable which when iterated returns the items of the collection in a particular order. An important difference. If preserving the original order of my array was important to me (spoiler: mostly it isn't) then I could make a call to
sort also returns the array to the caller which is nice for chaining and means we can use it in a similar fashion to the way we use
OrderBy. With that in mind, we're going to create comparer functions which will take a lambda / arrow function (ES6 alert!) and return a function which will compare based on the supplied lambda.
Let's start with ordering by string properties:
We need some example data to sort: (I can only apologise for my lack of inspiration here)
If we were doing a sort by strings in LINQ we wouldn't need to implement our own comparer. And the code we'd write would look something like this:
With that in mind, here's how it would look to use our shiny and new
Well that's strings sorted (quite literally). Now, what about numbers?
If we use the
numberComparer on our original array it looks like this:
Well this is all kinds of fabulous. But something's probably nagging at you... What about
OrderByDescending? What about when I want to sort in the reverse order? May I present the
As the name suggests, this function takes a given comparer that's handed to it and returns a function that inverts the results of executing that comparer. Clear as mud? A comparer can return 3 types of return values:
- 0 - implies equality for
- positive - implies
obj1is greater than
obj2by the ordering criterion
- negative - implies
obj1is less than
obj2by the ordering criterion
reverse function takes the comparer it is given and returns a new comparer that will return a positive value where the old one would have returned a negative and vica versa. (Equality is unaffected.) An alternative implementation would have been this:
Which is more optimal and even simpler as it just swaps the values supplied to the comparer. Whatever tickles your fancy. Either way, when used it looks like this:
If you'd rather not have a function wrapping a function inline then you could create
numberComparerDescending etc implementations. Arguably it might make for a nicer API. I'm not unhappy with the present approach myself and so I'll leave it as is. But it's an option.
So far we can sort arrays by strings, we can sort arrays by numbers and we can do either in descending order. It's time to take it to the next level people. That's right
ThenBy; I want to be able to sort by one criteria and then by a subcriteria. So perhaps I want to eat the food in the house in alphabetical order, but if I have multiple apples I want to eat the ones I bought most recently first (because the other ones look old, brown and yukky). This may also be a sign I haven't thought my life through, but it's a choice that people make. People that I know. People I may have married.
It's time to compose our comparers together. May I present... drum roll.... the
This fine function takes any number of comparers that have been supplied to it. It then returns a comparer function which, when called, iterates through each of the original comparers and executes them until it finds one that returns a value that is not 0 (ie represents that the 2 items are not equal). It then sends that non-zero value back or if all was equal then sends back 0.
composeComparers: The Sequel#
I'm not gonna lie - I was feeling quite pleased with this approach. I shared it with my friend (and repeated colleague) Peter Foldi. The next day I found this in my inbox:
Dammit he's improved it. It's down to 1 line of code, it doesn't execute a non-zero returning comparer twice and it doesn't rely on
The only criticism I can make of it is that it iterates through each of the comparers even when it doesn't need to execute them. But that's just carping really.
composeComparers: The Ultimate#
So naturally I thought I was done. Showing Peter's improvements to the estimable Matthew Horsley I learned that this was not so. Because he reached for the keyboard and entered this:
That's right, he's created a function which takes a number of comparers and reduced them up front into a single comparer function. This means that when the sort takes place there is no longer a need to iterate through the comparers, just execute them.
I'll get my coat...
You want to do this with TypeScript? Use this: