Create complex comparators, tersely and beautifully.
A comparator is a function that looks at two elements and decides which should go before the other in a sort. In practice, they're what you would pass to the array sort
functionβ
myArray.sort(myComparator);
βto tell sort
how to determine the elements' order.
A comparator receives two elements, a
and b
, and returns
-
A negative number, conventionally
-1
, ifa
should go beforeb
. -
A positive number, conventionally
1
, ifa
should go afterb
. -
0
if the two elements are equivalent.
In other words, a boilerplate comparator would look like
function(a, b) {
if(/* LOGIC DETERMINING THAT `a` GOES BEFORE `b` */) {
return -1;
}
else if(/* LOGIC DETERMINING THAT `a` GOES AFTER `b` */) {
return 1;
}
else {
return 0;
}
}
This boilerplate is not terse, and it doesn't scale well if you need to sort by multiple attributes.
generate-comparators
lets you define new comparators in just one line and combine them in another.
Install the package on the terminal using either npm or Yarn:
# With npm
npm install generate-comparators
# Or with Yarn
yarn add generate-comparators
Import the package's two functions into your Node project using either require
or, if you're transpiling ES6, import
:
// With `require`
const {comparators, composeComparators} = require('generate-comparators');
// Or with `import`
import {comparators, composeComparators} from 'generate-comparators';
Generates ascending and descending comparator functions given a function that converts array elements into easily comparable forms.
Returns an object with two properties, asc
and desc
.
-
asc : function
: a comparator for sorting an array in ascending order -
desc : function
: a comparator for sorting an array in descending order- NOTE: This is not necessarily the same as a reversed ascending order.
sort
sorts an array left-to-right, so when two equivalent elements are compared, the leftmost one will stay to the left.
- NOTE: This is not necessarily the same as a reversed ascending order.
NOTE: I recommend using the naming convention
byX
for this object, where X is the property or attribute the comparators check on. This has the advantage of being both terse and legible.
toComparable
must be a function that takes an element of an array and converts it into a form that can be more easily compared to other elements of the array. For instance, to create a comparator that would sort an array of elements by their lengths, use
(element) => element.length
for toComparable
.
toComparable
can be any function that performs any complex logic on the given element, so long as it returns a value in a comparable data type such as Number
, String
, or Date
.
- Sorting arrays of primitives using the identity function
element => element
(default behavior forarray.sort()
):
const {comparators} = require('generate-comparators');
const numbers = [5, 2, 7, -3, 0];
const strings = ['the', 'quick', 'brown', 'fox', 'jumped', 'over', 'the', 'lazy', 'dog'];
const byIdentity = comparators(element => element);
// Ascending
numbers.sort(byIdentity.asc); // [-3, 0, 2, 5, 7]
strings.sort(byIdentity.asc); // ['brown', 'dog', 'fox', 'jumped', 'lazy', 'over', 'quick', 'the', 'the']
// Descending
numbers.sort(byIdentity.desc); // [7, 5, 2, 0, -3]
strings.sort(byIdentity.desc); // ['the', 'the', 'quick', 'over', 'lazy', 'jumped', 'fox', 'dog', 'brown']
- Sorting an array of strings by length:
const {comparators} = require('generate-comparators');
const strings = ['the', 'quick', 'brown', 'fox', 'jumped', 'over', 'the', 'lazy', 'dog'];
const byLength = comparators(element => element.length);
// Ascending
strings.sort(byLength.asc); // ['the', 'fox', 'the', 'dog', 'over', 'lazy', 'quick', 'brown', 'jumped']
// Descending
strings.sort(byLength.desc); // ['jumped', 'quick', 'brown', 'over', 'lazy', 'the', 'fox', 'the', 'dog']
- Sorting an array of person objects by the number of vowels in their first and last name:
const {comparators} = require('generate-comparators');
const people = [
{lastName: 'Lovelace', firstName: 'Ada', birthYear: 1815},
{lastName: 'Turing', firstName: 'Alan', birthYear: 1936},
{lastName: 'Boole', firstName: 'George', birthYear: 1860},
{lastName: 'Babbage', firstName: 'Charles', birthYear: 1930}
];
const byVowelsInName = comparators(element => getVowelCount(element.firstName + element.lastName));
// Ascending
/* {lastName: 'Turing', firstName: 'Alan', birthYear: 1936},
* {lastName: 'Lovelace', firstName: 'Ada', birthYear: 1815},
* {lastName: 'Babbage', firstName: 'Charles', birthYear: 1930},
* {lastName: 'Boole', firstName: 'George', birthYear: 1860}
*/
people.sort(byVowelsInName.asc);
// Descending
/* {lastName: 'Boole', firstName: 'George', birthYear: 1860},
* {lastName: 'Lovelace', firstName: 'Ada', birthYear: 1815},
* {lastName: 'Babbage', firstName: 'Charles', birthYear: 1930},
* {lastName: 'Turing', firstName: 'Alan', birthYear: 1936}
*/
people.sort(byVowelsInName.desc);
Receives >0 comparators and combines them into new ascending and descending comparators.
The new comparators will first compare the two elements using comparators[0]
. If they are equivalent, they will be compared with comparators[1]
and so forth until all comparators have compared the elements. If the elements are still equivalent, the composed comparator will return 0
. Because the composed comparator iterates over the given comparators from left to right, the position of each comparator matters.
Returns an object with two properties, asc
and desc
.
-
asc : function
: a comparator for sorting an array in ascending order, using all given comparators -
desc : function
: a comparator for sorting an array in descending order, using all given comparators- NOTE: This is not necessarily the same as a reversed ascending order.
sort
sorts an array left-to-right, so when two equivalent elements are compared, the leftmost one will stay to the left.
- NOTE: This is not necessarily the same as a reversed ascending order.
- Sorting an array of person objects first by age (descending), then by last name (ascending), then by first name (descending)
const {comparators, composeComparators} = require('generate-comparators');
const people = [
{lastName: 'Doe', firstName: 'John', age: 42},
{lastName: 'Boole', firstName: 'George', age: 67},
{lastName: 'Lovelace', firstName: 'Ada', age: 2},
{lastName: 'Doe', firstName: 'Jane', age: 42},
{lastName: 'Hamilton', firstName: 'Margaret', age: 100},
];
const byAge = comparators(element => element.age);
const byLastName = comparators(element => element.lastName);
const byFirstName = comparators(element => element.firstName);
const composed = composeComparators(byAge.desc, byLastName.asc, byFirstName.desc);
// Ascending
/* {lastName: 'Hamilton', firstName: 'Margaret', age: 100},
* {lastName: 'Boole', firstName: 'George', age: 67},
* {lastName: 'Doe', firstName: 'John', age: 42},
* {lastName: 'Doe', firstName: 'Jane', age: 42},
* {lastName: 'Lovelace', firstName: 'Ada', age: 2}
*/
people.sort(composed.asc);
// Descending
/* {lastName: 'Lovelace', firstName: 'Ada', age: 2},
* {lastName: 'Doe', firstName: 'Jane', age: 42},
* {lastName: 'Doe', firstName: 'John', age: 42},
* {lastName: 'Boole', firstName: 'George', age: 67},
* {lastName: 'Hamilton', firstName: 'Margaret', age: 100}
*/
people.sort(composed.desc);