comparator-factory-factory
Create comparison functions to be used for sorting arrays.
This is a dedicated tiny library, not aiming at a framework.
Features
- Available for both Node.js and browsers (including IE 11)
- Function-based comparison value selection
- Property-path-based comparison value selection can be implemented easily
- Handling
undefined
, null
, NaN
as first or last - String collation using native
Intl.Collator
- Chaining comparison functions
- Designed to share the comparison rule in the product;
Creating not a comparison function directly but a comparison function factory that may be shared - Lightweight (~1.7kB minified, ~0.7kB gzipped)
Install
via npm
$ npm install comparator-factory-factory
import comparatorFactoryFactory from "comparator-factory-factory";
const comparing = comparatorFactoryFactory();
[].sort(comparing());
via CDN
<script src="https://cdn.jsdelivr.net/npm/comparator-factory-factory@0.2.1"></script>
<script>
const comparing = comparatorFactoryFactory();
[].sort(comparing());
</script>
or for modern browsers:
<script type="module">
import comparatorFactoryFactory from "https://cdn.jsdelivr.net/npm/comparator-factory-factory@0.2.1/index.min.mjs";
const comparing = comparatorFactoryFactory();
[].sort(comparing());
</script>
Usage & Examples
const comparing = comparatorFactoryFactory({
specials: [[null, "last"]],
collator: { caseFirst: "upper", numeric: true },
});
["A5", "A1", null, "A3", "A10", "a3", "A7"].sort(comparing());
["A5", "A1", null, "A3", "A10", "a3", "A7"].sort(comparing().reversed());
const users = [
{ id: "01", name: "Alice", profile: { age: 17 } },
{ id: "02", name: "Bob" },
{ id: "03", profile: { age: 16 } },
{ id: "04", name: "alice", profile: { age: 15 } },
{ id: "05", name: "bob", profile: { age: 18 } },
{ id: "06", name: "Bob", profile: { age: 15 } },
];
{
const comparing = comparatorFactoryFactory();
users.sort(comparing(x => [x.profile.age, x.id]));
}
{
const comparing1 = comparatorFactoryFactory({
specials: [[undefined, "last"]],
collator: { sensitivity: "base" },
});
const comparing2 = comparatorFactoryFactory({
specials: [[undefined, "last"]],
});
users.sort(
comparing1(x => x.name)
.reversed()
.or(comparing2(x => x.profile.age))
.or(comparing2(x => x.id))
);
}
{
const comparingPropertyPath = comparatorFactoryFactory({
selector(fullpath) {
const paths = fullpath.replace(/\[(\d+)]/g, ".$1").split(".").filter(Boolean);
return obj => paths.every(path => (obj = obj[path]) != null) && obj;
},
});
users.sort(comparingPropertyPath("profile.age", "id"));
}
API
Summary
const comparatorFactory = comparatorFactoryFactory({
selector: key => obj => comparisonResult,
specials: [
[undefined, "first"],
[null, "first"],
[NaN, "first"],
],
collator: {
locales: undefined,
sensitivity: "variant",
numeric: false,
caseFirst: "false",
},
});
const comparator = comparatorFactory(key1, key2, ...);
const comparisonResult = comparator(obj1, obj2);
const reversedComparator = comparator.reversed();
const comparatorItself = comparator.reversed(false);
const combinedComparator = comparator.or((obj1, obj2) => number);
comparatorFactoryFactory({ selector?, specials?, locales?, collator? }) => comparatorFactory
Create a comparison function factory based on the specified rule.
Parameters
-
selector: key => obj => comparisonResult
A function selecting comparison value from key
and obj
.
The receiving parameter key
is each argument of comparatorFactory(key1, key2, ...)
.
The receiving parameter obj
is each argument of comparator(obj1, obj2)
.
The default implementation is as follows.
key => obj => {
try {
return key(obj);
} catch {
return undefined;
}
}
Following code is a property-path-based comparison example using lodash/get. (BTW, there are so many similar modules.)
const get = require("lodash/get");
const comparingPropertyPath = comparatorFactoryFactory({
selector: key => obj => get(obj, key),
});
const users = [
{ id: 1, profile: { age: 18 } },
{ id: 2, profile: { age: 15 } },
];
users.sort(comparingPropertyPath("profile.age", "id"));
-
specials: [[value1, "first" (or "last")], [value2, "first" (or "last")], ...]
Special values to place first or last.
The default value is as follows.
[
[undefined, "first"],
[null, "first"],
[NaN, "first"],
]
-
collator: { locales?, sensitivity?, numeric?, caseFirst? } | { compare: (string1, string2) => number }
String comparison method.
Possible values are as follows.
- An options object for
Intl.Collator
constructor with optional property locales
- An object that has
compare(string1, string2) => number
method (a Intl.Collator
instance does)
See Intl.Collator at MDN for details.
The default value is the default Intl.Collator()
.
comparatorFactory(key1, key2, ...) => comparator
Create a comparison function.
Parameters
comparator(obj1, obj2) => number
Evaluate.
0 if obj1
and obj2
are considered to be equal,
a negative number if obj1
is considered to be smaller than obj2
,
a positive number if obj1
is considered to be larger than obj2
.
comparator.reversed(really? = true) => comparator
Create a comparison function with reverse order.
comparator.or((obj1, obj2) => number) => comparator
Create a combined comparison function.
The combined comparison function evaluates the original comparator(obj1, obj2)
first.
If that result equals to 0 (or falsy), it evaluates specified comparison function next.
Limitation
Array.prototype.sort()
always put the undefined
at the end of the array.
This behavior is specified in the ECMAScript specification.
const comparing = comparatorFactoryFactory({
specials: [[undefined, "first"], [null, "first"], [NaN, "first"]],
});
[{ id: 3 }, { id: 1 }, { id: undefined }, { id: 7 }].sort(comparing(x => x.id));
[3, 1, null, 7].sort(comparing());
[3, 1, NaN, 7].sort(comparing());
[3, 1, undefined, 7].sort(comparing());
License
WTFPL
Similar modules