javascript - 如何对每个项目的属性值具有不同优先级和排序方向规则但也可以未定义的多属性数组项进行排序?
问题描述
我有一组员工,每个员工至少有 fullName 和employeeId。有些员工有crewNumber 和cmpId。
这种排序发生在 NestJS 服务器上。客户端有一个 AG-Grid,它传递需要在服务器上的数据上发生的排序数组。
当涉及到数字、字符串,尤其是某些数字属性的未定义值时,我无法弄清楚如何在服务器上、多个“列”或“属性”上对数组进行排序。
const employees = [
{
"employeeId": "JACKAB",
"fullName": "Jack Absolute",
"cmpId": 2
},
{
"employeeId": "BLABLA",
"fullName": "Joe Smith"
},
{
"employeeId": "FORFIVE",
"fullName": "Tom Scott",
"cmpId": 109
},
{
"employeeId": "RONBURN",
"fullName": "Morty Smith"
},
];
console.log("employees before sorting: ", employees)
const sortBy = [
{ prop: 'fullName', direction: 1, sortIndex: 0 },
{ prop: 'employeeId', direction: -1, sortIndex: 1 },
{ prop: 'cmpId', direction: 1, sortIndex: 2 }
];
employees.sort(function (a, b) {
let i = 0, result = 0;
while (i < sortBy.length && result === 0) {
const prop = sortBy[i].prop;
const dataA = a[prop];
const dataB = b[prop];
const direction = sortBy[i].direction;
const numberProperties = ["cmpId", "crewNumber"];
const isNumber = numberProperties.includes(prop);
if (dataA == undefined && dataB == undefined) {
result = 0;
}
else if (dataA == undefined) {
result = -1 * direction
}
else if (dataB == undefined) {
result = direction;
}
else {
if (isNumber) {
result = direction * (dataA < dataB ? -1 : (dataA > dataB ? 1 : 0));
}
else {
result = direction * Intl.Collator().compare(dataA, dataB);
}
}
i++;
}
return result;
})
console.log("employees after sorting: ", employees);
如果我在具有未定义值的列上上升,我希望未定义值位于顶部。如果我下降到具有未定义值的列,我希望底部的未定义值。
目前的问题是while循环在结果!= 0时取消,并且在检查dataA是否未定义或dataB未定义时发生。这会导致 while 循环不会遍历需要发生的其余类型。
我尝试在其中一个未定义时返回 0,但这会导致未定义的值不会移动到任何地方。
解决方案
由于简单的对象项目,这里的employee
项目,需要通过一些作为配置对象提供的规则进行比较,所以人们真的想通过一种通用的方法来解决这个问题,因此将其分解为小步骤/任务并提供一个方便的功能对于每个任务。
规则以配置对象列表的形式出现,每个都提供比较术语...
- 每个规则通过自己的
sortIndex
属性提供其排序顺序优先级/重要性。 - 提供了一个
prop
erty 名称,从而标识将要比较对象的key
值。 direction
指示是按升序(1
)还是降序(-1
)排序。
了解这一切后,首先要编写两个几乎相似的比较函数,每个函数都考虑了一个绑定的key
配置对象。因此,这样的函数将针对其传递的两个项目比较key
指示的属性值。
这些函数被命名为compareByBoundKeyAscending
和compareByBoundKeyDescending
。两者都尝试利用localeCompare
第一个传递参数的方法。如果这样的方法不可用,则排序会退回到通用比较,无论是在其升序还是在其降序实现中。两个备用函数是basicCompareAscending
和basicCompareDescending
。
现在已经可以开始使用第一个通用比较函数... sortBy
OP 示例代码的数组,比较项列表,按其每个术语项的sortIndex
键的值升序排序...
sortBy
.sort(compareByBoundKeyAscending
.bind({
key: 'sortIndex',
})
)
现在有了一组比较项,每个项已经按照其重要性的正确顺序排列,现在将映射每个比较项,同时为每个稍后要比较的对象的key
值创建一个自定义比较函数一对 ...
const precedenceConditionList = sortBy
.sort(compareByBoundKeyAscending
.bind({
key: 'sortIndex',
})
)
.map(({ prop: key, direction }) => ({
"1": compareByBoundKeyAscending.bind({ key }),
"-1": compareByBoundKeyDescending.bind({ key }),
})[direction]
);
...它precedenceConditionList
本身是一个自定义比较函数数组,按每个函数的重要性排序,其中每个函数通过这些项目的专用属性值升序或降序比较传递的项目。
当然,人们仍然需要一个函数,它确实比较了诸如OP数组中提供employee
的 -items 之类的东西。employees
这个函数确实对precedenceConditionList
我们刚刚在上面创建的绑定条件列表进行操作。它的实现也是通用的,并利用了仅有的 3 个可能的返回值-1
、1
和0
。只要操作的绑定列表中的条件不返回-1
or 1
but 0
,就需要调用绑定列表中的下一个可用条件。因此,人们可以很容易地利用Array.prototype.some
它来检索正确的返回值,并尽可能早地从循环条件中中断......
function compareByBoundConditionList(a, b) {
let result = 0;
this.some(condition => {
result = condition(a, b);
return (result !== 0);
})
return result;
}
示例代码...
// ... introduce `compareIncomparables` ...
//
// in order to solve the OP's problem of a meaningful
// sorting while dealing with undefined property values.
//
function compareIncomparables(a, b) {
const index = {
'undefined': 5,
'null': 4,
'NaN': 3,
'Infinity': 2,
'-Infinity': 1,
};
return (index[String(a)] || 0) - (index[String(b)] || 0);
}
function basicCompareAscending(a, b) {
//return ((a < b) && -1) || ((a > b) && 1) || 0;
return (
((a < b) && -1) ||
((a > b) && 1) ||
// try to furtherly handle incomparable values like ...
// undefined, null, NaN, Infinity, -Infinity or object types.
(a === b) ? 0 : compareIncomparables(a, b)
);
}
function basicCompareDescending(a, b) {
//return ((a > b) && -1) || ((a < b) && 1) || 0;
return (
((a > b) && -1) ||
((a < b) && 1) ||
// try to furtherly handle incomparable values like ...
// undefined, null, NaN, Infinity, -Infinity or object types.
(a === b) ? 0 : compareIncomparables(a, b)
//(a === b) ? 0 : compareIncomparables(b, a)
);
}
function compareByBoundKeyAscending(a, b) {
const { key } = this;
const aValue = a[key];
const bValue = b[key];
// take care of undefined and null values as well as of
// other values which do not feature a `localeCompare`.
return aValue?.localeCompare
? aValue.localeCompare(bValue)
: basicCompareAscending(aValue, bValue);
}
function compareByBoundKeyDescending(a, b) {
const { key } = this;
const aValue = a[key];
const bValue = b[key];
// take care of undefined and null values as well as of
// other values which do not feature a `localeCompare`.
return bValue?.localeCompare
? bValue.localeCompare(aValue)
: basicCompareDescending(aValue, bValue);
}
function compareByBoundConditionList(a, b) {
let result = 0;
this.some(condition => {
result = condition(a, b);
return (result !== 0);
});
return result;
}
const employees = [{
"employeeId": "FORFIVE",
"fullName": "Tom Scott",
}, {
"employeeId": "BLABLA",
"fullName": "Joe Smith",
}, {
"employeeId": "FORFIVE",
"fullName": "Tom Scott",
"cmpId": 109
}, {
"employeeId": "JACKAB",
"fullName": "Jack Absolute",
"cmpId": 4
}, {
"employeeId": "JACKAB",
"fullName": "Jack Absolute",
"cmpId": 2
}, {
"employeeId": "RONBURN",
"fullName": "Morty Smith",
}, {
"employeeId": "BLABLU",
"fullName": "Joe Smith",
}];
const sortBy = [
{ prop: 'fullName', direction: 1, sortIndex: 0 },
{ prop: 'employeeId', direction: -1, sortIndex: 1 },
{ prop: 'cmpId', direction: 1, sortIndex: 2 },
];
const precedenceConditionList = sortBy
.sort(compareByBoundKeyAscending
.bind({
key: 'sortIndex',
})
)
.map(({ prop: key, direction }) => ({
"1": compareByBoundKeyAscending.bind({ key }),
"-1": compareByBoundKeyDescending.bind({ key }),
})[direction]
);
console.log(
employees.sort(compareByBoundConditionList
.bind(precedenceConditionList)
)
);
.as-console-wrapper { min-height: 100%!important; top: 0; }
推荐阅读
- node.js - 为什么当我 cat `/usr/local/bin` 中的节点目录时,它会释放 cthulhu?
- c# - 如何填写 XFA pdf 文档的单个字段?
- android - 如何直接打开华为应用市场?
- python - 如何在 Python 中为非静态字段使用描述符(或类似的东西)而不丢失封装?
- django - request.FILES 为空,但请求中存在文件
- scala - 如何将 Slick 依赖项与 Lagom 绑定?
- zomato-api - 为什么 Zomato API 按邮政编码搜索时出现问题?
- unit-testing - 我是否需要为没有逻辑的服务方法编写单元测试?
- python-3.x - 如何同时使用不和谐机器人命令和事件?
- postgresql - postgresql:自加入数组