如何对多个 属性 数组项进行排序,其中每个项的 属性 值具有不同的优先级和排序方向规则,但也可以未定义?
How to sort multi-property array-items where each item's property-value has different precedence and sort orientation rules but can be undefined too?
我有一组员工,每个员工至少有全名和员工 ID。
有些员工有 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
项,需要通过一些作为配置对象提供的规则进行比较,人们真的想通过一种通用的方法解决这个问题,因此打破了它分成小 steps/tasks 并为每个任务提供方便的功能。
规则以配置对象列表的形式出现,每个都提供比较项...
- 每个规则通过自己的
sortIndex
属性. 提供其排序顺序 precedence/importance
- 提供了一个
prop
erty 名称,从而标识将要比较对象的 key
-value。
direction
表示升序(1
)还是降序(-1
)排序。
知道了这一切,我们开始编写两个几乎相似的比较函数,每个比较函数都考虑了绑定的 key
配置对象。因此,对于它传递的两个项目,这样的函数将比较 key
指示的 属性 值。
函数名为 compareByBoundKeyAscending
和 compareByBoundKeyDescending
。两者都尝试利用第一个传递参数的 localeCompare
方法。如果这样的方法不可用,则排序回退到一般比较,无论是在其升序还是在其降序实现中。两个回退函数是 basicCompareAscending
和 basicCompareDescending
.
现在已经可以开始使用第一个通用比较函数了...OP 示例代码的 sortBy
数组(比较项列表)按每个项的值升序排序-item 的 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 的 employees
数组中提供的 employee
项之类的东西。
此函数确实对绑定条件列表进行操作,例如我们刚刚在上面创建的 precedenceConditionList
。它的实现也是通用的,并利用了 3 个可能的 return 值 -1
、1
和 0
。只要操作绑定列表中的条件不是 return -1
或 1
而是 0
,就需要调用绑定列表中的下一个可用条件。因此,可以很容易地利用 Array.prototype.some
来检索正确的 return 值,并尽早从循环条件中中断 ...
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; }
我有一组员工,每个员工至少有全名和员工 ID。 有些员工有 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
项,需要通过一些作为配置对象提供的规则进行比较,人们真的想通过一种通用的方法解决这个问题,因此打破了它分成小 steps/tasks 并为每个任务提供方便的功能。
规则以配置对象列表的形式出现,每个都提供比较项...
- 每个规则通过自己的
sortIndex
属性. 提供其排序顺序 precedence/importance
- 提供了一个
prop
erty 名称,从而标识将要比较对象的key
-value。 direction
表示升序(1
)还是降序(-1
)排序。
知道了这一切,我们开始编写两个几乎相似的比较函数,每个比较函数都考虑了绑定的 key
配置对象。因此,对于它传递的两个项目,这样的函数将比较 key
指示的 属性 值。
函数名为 compareByBoundKeyAscending
和 compareByBoundKeyDescending
。两者都尝试利用第一个传递参数的 localeCompare
方法。如果这样的方法不可用,则排序回退到一般比较,无论是在其升序还是在其降序实现中。两个回退函数是 basicCompareAscending
和 basicCompareDescending
.
现在已经可以开始使用第一个通用比较函数了...OP 示例代码的 sortBy
数组(比较项列表)按每个项的值升序排序-item 的 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 的 employees
数组中提供的 employee
项之类的东西。
此函数确实对绑定条件列表进行操作,例如我们刚刚在上面创建的 precedenceConditionList
。它的实现也是通用的,并利用了 3 个可能的 return 值 -1
、1
和 0
。只要操作绑定列表中的条件不是 return -1
或 1
而是 0
,就需要调用绑定列表中的下一个可用条件。因此,可以很容易地利用 Array.prototype.some
来检索正确的 return 值,并尽早从循环条件中中断 ...
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; }