首页 > 解决方案 > 如何对每个项目的属性值具有不同优先级和排序方向规则但也可以未定义的多属性数组项进行排序?

问题描述

我有一组员工,每个员工至少有 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,但这会导致未定义的值不会移动到任何地方。

标签: javascriptarrayssortingcomparison

解决方案


由于简单的对象项目,这里的employee项目,需要通过一些作为配置对象提供的规则进行比较,所以人们真的想通过一种通用的方法来解决这个问题,因此将其分解为小步骤/任务并提供一个方便的功能对于每个任务。

规则以配置对象列表的形式出现,每个都提供比较术语...

  • 每个规则通过自己的sortIndex属性提供其排序顺序优先级/重要性。
  • 提供了一个property 名称,从而标识将要比较对象的key值。
  • direction指示是按升序(1)还是降序(-1)排序。

了解这一切后,首先要编写两个几乎相似的比较函数,每个函数都考虑了一个绑定的key配置对象。因此,这样的函数将针对其传递的两个项目比较key指示的属性值。

这些函数被命名为compareByBoundKeyAscendingcompareByBoundKeyDescending。两者都尝试利用localeCompare第一个传递参数的方法。如果这样的方法不可用,则排序会退回到通用比较,无论是在其升序还是在其降序实现中。两个备用函数是basicCompareAscendingbasicCompareDescending

现在已经可以开始使用第一个通用比较函数... sortByOP 示例代码的数组,比较项列表,按其每个术语项的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 个可能的返回值-110。只要操作的绑定列表中的条件不返回-1or 1but 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; }


推荐阅读