开发者

Javascript sort custom comparator function - sorting a sorted array

I have an array of objects of the following form:

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

I sort it first by 'item1' which is straightforward. Now i want to sort arr (which is sorted by 'item1') again but this time by 'item2' but only for the elements where 'item1' is the same. The fi开发者_高级运维nal array would look like:

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

I tried to write a sorting function for the second case like so:

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

I could combine the two sorts in one function to get the final sorted array but there will be cases where I'll have to sort by just 'item1' or just 'item2'.


You can have four different comparison functions - one sorting by item1, one by item2, one by item1 then item2 and one by item2 then item1.

E.g.:

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;
});


I hit the same question lately. Came with a similar solution than langpavel, but I prefer to split the thing in two. First a chained comparator helper that will allows multiple sort rule, each applied in order as a tie-breaker in case of equality:

    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;
        };
    }

I like it, because it takes and return sort comparator. So if you have a collection of other comparators, they are easy to use.

Then you can simplify a bit your life with an additional helper. This one return a sort comparator based on the result of the passed lambda over each items.

    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 here is not required to answer the question, but will allow to reverse the order easily.

To answer the question specifically, using our two comparators:

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

Because the original question was in pure JavaScript, precision: If you're not accustomed to TypeScript, you can get normal JavaScript just by removing the typing <T>, : T, : ((item: T) => Comparable) everywhere and the two type lines out.


I'm using this helper in 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,
));


You can just import type-comparator with npm and then use queue to do the chaining:

const comparator = queue([
    map(x => x.item1, asc),
    map(x => x.item2, asc)
]);
arr.sort(comparator);


Or as simple oneliner for first and second priority sort, you can expand it further as you wish, just replace the 0 with another comparison chain. Switch < and > or -1 and 1 for the reversed order.

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;
});
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜