JS sorting by two values

Viewed 61

I need sort array with Bustype and index

What I have

[
  { BusType: "SATA", index: 1, Id: "SATA.1" },
  { BusType: "SATA", index: 10, Id: "SATA.10" },
  { BusType: "IDE", index: 1, Id: "IDE.1" },
  { BusType: "IDE", index: 2, Id: "IDE.2" },
  { BusType: "IDE", index: 10, Id: "IDE.10" },
]

Expected ouput:

[
  { BusType: "IDE", index: 1, Id: "IDE.1" },
  { BusType: "IDE", index: 2, Id: "IDE.2" },
  { BusType: "IDE", index: 10, Id: "IDE.10" },
  { BusType: "SATA", index: 1, Id: "SATA.1" },
  { BusType: "SATA", index: 10, Id: "SATA.10" }
]

I tried this:

arr.sort((a, b) => a.Id.localeCompare(b.Id))

but got this :

[
  { BusType: "IDE", index: 1, Id: "IDE.1" },
  { BusType: "IDE", index: 10, Id: "IDE.10" },
  { BusType: "IDE", index: 2, Id: "IDE.2" },
  { BusType: "SATA", index: 1, Id: "SATA.1" },
  { BusType: "SATA", index: 10, Id: "SATA.10" }
]

Sorting like alphabet.. 1 10 100 2 20 3

how to sort in right derection?

4 Answers
arr.sort((a, b) => a.BustType.localeCompare(b.BustType) || a.index - b.index);

Which will sort by BustType and if that returns 0 (meaning they have the same BusType), then the right side of || kicks in and a.index - b.index is used instead which will sort by index. This is called short-circuit evaluation.

You could implement a multi-sort.

You just need to declare some sorters. These are just a way to tell the sorting function about your fields i.e. what type and which direction is to be sorted.

Here is an example sorter:

/**
 * @param {String} name - Object field to be compared
 * @param {String=['string'],'int'} type - Value type
 * @param {String=['asc'],'desc'} dir - Sort direction
 * @param {Function} fn - Optional comparator function
 */
const exampleSorter = {
  name : 'field',
  type : 'string',
  dir  : 'asc'
  fn   : (a, b) => a - b
}

Sorters can be:

  • A single string field name
  • A mixed array of field names or sorter objects

Demo

const busData = [
  { BusType: "SATA" , index: 1  , Id: "SATA.1"  },
  { BusType: "SATA" , index: 10 , Id: "SATA.10" },
  { BusType: "IDE"  , index: 1  , Id: "IDE.1"   },
  { BusType: "IDE"  , index: 2  , Id: "IDE.2"   },
  { BusType: "IDE"  , index: 10 , Id: "IDE.10"  },
]

const multiSort = (data, sorters) => {
  const defaultSorter = { type: 'string', dir: 'asc' }
  const compare = (a, b, sorter) => {
    let field = sorter.name
    if (b == null || b[field] == null) return -1
    if (a == null || a[field] == null) return 1
    if (sorter.fn) return sorter.fn.call(sorter, a[field], b[field])
    switch (sorter.type) {
      case 'int':     return a[field] - b[field];
      case 'string':
      default:        return a[field].localeCompare(b[field])
    }
  }
  if (typeof sorters === 'string') sorters = [sorters]
  sorters = sorters.map(sorter => typeof sorter === 'string'
      ? { ...defaultSorter, name: sorter }
      : { ...defaultSorter, sorter })
  return data.sort((a, b) => {
    for (let i = 0; i < sorters.length; i++) {
      let sorter = sorters[i], result = compare(a, b, sorter)
      if (result !== 0) return result * (sorter.dir === 'desc' ? -1 : 1)
    }
    return 0
  })
}

console.log(multiSort(busData, [
  { name: 'BusType', fn: (a, b) => a.localeCompare(b) },
  { name: 'index', type: 'int', dir: 'asc' }
]))
.as-console-wrapper { top: 0; max-height: 100% !important; }

Try this:

arr.sort(
  (a,b)=> {
    a.BusType<b.BusType?-1:
    a.BusType>b.BusType?1:
    a.index<b.index?-1:
    a.index>b.index?1:
    0;
  }
);

We compare BusType, -1 if a's is smaller, 1 if a's is bigger, and if equal:
we compare index, -1 if a's is smaller, 1 if a's is bigger, and if equal: 0.

.sort was a good choice:

const a = [
  {BusType: "SATA", index: 1, Id: "SATA.1"},
  {BusType: "SATA", index: 10, Id: "SATA.10"},
  {BusType: "IDE", index: 1, Id: "IDE.1"},
  {BusType: "IDE", index: 2, Id: "IDE.2"},
  {BusType: "IDE", index: 10, Id: "IDE.10"},
];
const res = a.slice(); // so we don't change original Array
res.sort((a, b)=>{
  return a.BusType.localeCompare(b.BusType) || +a.index.toString().match(/^[0-9]/)[0] > +b.index.toString().match(/^[0-9]/)[0]
});
console.log(res);

Related