Home > Mobile >  How to stably sort an array of objects by a series of keys such that ordering of each preceding key
How to stably sort an array of objects by a series of keys such that ordering of each preceding key

Time:12-21

I would like to build a general purpose function that can sort an array of objects using a list of keys such that the ordering of the previous key is preserved. Here is an example:

const input = [
  { a: 'aardvark', b: 'bear', c: 'camel', d: 1 },
  { a: 'anemone', b: 'bat', c: 'cobra', d: 6 },
  { a: 'aardvark', b: 'badger', c: 'camel', d: 2 },
  { a: 'alligator', b: 'bat', c: 'chicken', d: 2 },
  { a: 'alligator', b: 'beetle', c: 'cow', d: 1 },
  { a: 'alligator', b: 'bat', c: 'crab', d: 3 },
]

sortFunction(['a', 'b', 'd'], input)

// output
[
  { a: 'aardvark', b: 'badger', c: 'camel', d: 2 },
  { a: 'aardvark', b: 'bear', c: 'camel', d: 1 },
  { a: 'alligator', b: 'bat', c: 'chicken', d: 2 },
  { a: 'alligator', b: 'bat', c: 'crab', d: 3 },
  { a: 'alligator', b: 'beetle', c: 'cow', d: 1 },
  { a: 'anemone', b: 'bat', c: 'cobra', d: 6 },
]

The first key to sort on is, a, so the output first correctly sorts the data by a in descending order. The next key in the list is b, so for any item where the value of a is the same, the b keys are used to reorder only the items where a is the same. Finally, d is used to reorder only items where the value of a and b are the same. In this particular example, there are 2 items with the values of a: alligator and b: bat, so the item containing d: 2 is placed before the item containing d: 3.

The issue I'm struggling with is how to make this into a generic function that can accept any array of objects and sort them by a list of keys where the value of the keys are either string on numbers as I've show in this example.

CodePudding user response:

One approach is to call the sort() method on your input with a comparator callback that compares by each key in order until it finds one that determines the sort order:

function sortFunction<K extends PropertyKey, T extends Record<K, string | number>>(
  keys: K[], objs: T[]
) {
  objs.sort((a, b) => {
    for (const k of keys) {
      if (a[k] < b[k]) return -1;
      if (a[k] > b[k]) return 1;
    }
    return 0;
  })
}

It's a generic function that constrains the objs input so that the array of objects is known to have string or number values at the keys in the keys array.

You can verify that this works as desired on your example input:

sortFunction(['a', 'b', 'd'], input);

console.log(input.map(v => JSON.stringify(v)).join("\n"));    
/*
"{"a":"aardvark","b":"badger","c":"camel","d":2}
{"a":"aardvark","b":"bear","c":"camel","d":1}
{"a":"alligator","b":"bat","c":"chicken","d":2}
{"a":"alligator","b":"bat","c":"crab","d":3}
{"a":"alligator","b":"beetle","c":"cow","d":1}
{"a":"anemone","b":"bat","c":"cobra","d":6}" ]
*/

Playground link to code

  • Related