JavaScript排序自定义比较函数 - 对已排序的数组进行排序

38

我有一个对象数组,每个对象的形式如下:

arr[0] = { 'item1' : 1234, 'item2' : 'a string' };

我首先按照'item1'进行排序,这很直接。现在我想再次对arr(已经按'item1'排序)进行排序,但这次是按'item2'排序,但仅适用于'item1'相同的元素。最终数组应如下所示:

arr = [
  { 'item1' : 1234, 'item2' : 'apple' },
  { 'item1' : 1234, 'item2' : 'banana' },
  { 'item1' : 1234, 'item2' : 'custard' },
  { 'item1' : 2156, 'item2' : 'melon' },
  { 'item1' : 4345, 'item2' : 'asparagus' } 
];

我尝试编写第二种情况的排序函数,代码如下:

arr.sort(function(a,b){
  if(a.item1 === b.item1){
    return a.item2 > b.item2 ? 1 : a.item2 < b.item2 : -1 : 0;
  }
});

我可以将这两种排序方式合并为一个函数,以获得最终排序的数组,但有些情况下我只需要按照'item1'或者'item2'进行排序。


如果您已经按item1、item2和它们两个进行了排序,那么您到底在问什么? - Eran Zimmerman Gonen
就像编写一个装饰器模式,我可以说“new sortByItem2(new sortByItem1(arr))”。我还想像使用sortByItem1(arr)或sortByItem2(arr)一样使用它,或者是“new sortByItem1(new sortByItem2(arr))”。 - Parag
5个回答

49

你可以有四个不同的比较函数,一个按照item1排序,一个按照item2排序,一个先按照item1再按照item2排序,一个先按照item2再按照item1排序。

例如:

arr.sort(function(a,b){
  if(a.item1 == b.item1){
    return a.item2 > b.item2 ? 1 : a.item2 < b.item2 ? -1 : 0;
  }

  return a.item1 > b.item1 ? 1 : -1;
});

2
太棒了,JavaScript中可以如此简单地使用比较器在排序函数中。 - changtung

8

我最近也遇到了同样的问题。提供了与langpavel类似的解决方案,但我更喜欢将其分成两部分。首先是链接比较器助手,它允许多个排序规则,每个规则按顺序应用于在平等情况下作为绑定器的情况:

    type Comparator<T> = (a: T, b: T) => number; // -1 | 0 | 1

    /**
     * Allow to chain multiple comparators, each one called to break equality from the previous one.
     */
    function chainedComparator<T>(...comparators: Comparator<T>[]): Comparator<T> {
        return (a: T, b: T) => {
            let order = 0;
            let i = 0;
    
            while (!order && comparators[i]) {
                order = comparators[i++](a, b);
            }
    
            return order;
        };
    }

我喜欢它,因为它接受并返回排序比较器。因此,如果您有其他比较器的集合,它们很容易使用。

然后,您可以通过添加一个额外的 helper 稍微简化生活。该 helper 基于传递的 lambda 对每个项的结果返回一个排序比较器。

    type Comparable = string | number;

    /**
     * Returns a comparator which use an evaluationFunc on each item for comparison
     */
    function lambdaComparator<T>(evaluationFunc: ((item: T) => Comparable), reversed = false): Comparator<T> {
        return (a: T, b: T) => {
            const valA = evaluationFunc(a);
            const valB = evaluationFunc(b);
            let order = 0;
    
            if (valA < valB) {
                order = -1;
            } else if (valA > valB) {
                order = 1;
            }
            return reversed ? -order : order;
        };
    }

reversed 这里并不是必须的,但它可以方便地反转顺序。

具体回答问题时,使用我们的两个比较器:

    arr.sort(chainedComparator(
        lambdaComparator(a => a.item1),
        lambdaComparator(a => a.item2.toLowerCase()) // "banana" before "Melon"
    ));

由于原始问题是在纯JavaScript中,如果您不习惯TypeScript,只需删除所有类型<T>: T: ((item: T) => Comparable)和两个type行,就可以得到正常的JavaScript。


1
scoreAlambdaComparator中应该改为valAB也是一样。 - Samoth

3

我正在TypeScript中使用这个帮助类:

// Source
type ComparatorSelector<T> = (value: T, other: T) => number | string | null;

export function createComparator<T>(...selectors: ComparatorSelector<T>[]) {
  return (a: T, b: T) => {
    for (const selector of selectors) {
      const valA = selector(a, b);
      if (valA === null) continue;
      const valB = selector(b, a);
      if (valB === null || valA == valB) continue;
      if (valA > valB) return 1;
      if (valA < valB) return -1;
    }
    return 0;
  };
}

// Usage:
const candidates: any[] = [];
// ...
candidates.sort(createComparator(
  (x) => x.ambiguous,
  (_, y) => y.refCount, // DESC
  (x) => x.name.length,
  (x) => x.name,
));

1
你可以使用npm导入type-comparator,然后使用queue进行链接操作:
const comparator = queue([
    map(x => x.item1, asc),
    map(x => x.item2, asc)
]);
arr.sort(comparator);

0

或者对于第一和第二优先级排序,您可以将其扩展为所需的任何程度,只需使用另一个比较链替换0即可。将<>-11 互换以逆序排列。

someArray.sort(function(a,b) {
  return a.item1 > b.item1 ? 1 : a.item1 < b.item1 ? -1 : a.item2 > b.item2 ? 1 : a.item2 < b.item2 ? -1 : 0;
});

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接