Sort simple array with a simple condition (Snippet Included)

Viewed 49

I'm trying to sort the cars by model and month and it works well. But I have an average month 00 which I want to be the last record of each model instead of the first.

I have no idea how to achieve this. I have created a snippet. Any help please?

let cars = [{
    model: 'Ford',
    month: '12'
  }, {
    model: 'Ford',
    month: '03'
  }, {
    model: 'Ford',
    month: '01'
  }, {
    model: 'Ford',
    month: '00'
  },
  {
    model: 'Ford',
    month: '05'
  },
  {
    model: 'Fiat',
    month: '05'
  },
  {
    model: 'Fiat',
    month: '01'
  },
  {
    model: 'Fiat',
    month: '00'
  }
];

console.log(`Unsorted cars: ${JSON.stringify(cars)}`);

const sorted = cars.sort((a, b) => a.model.localeCompare(b.model) || a.month.localeCompare(b.month));

console.log(`Sorted cars: ${JSON.stringify(sorted)}`);

Got:

[{"model":"Fiat","month":"00"},{"model":"Fiat","month":"01"},{"model":"Fiat","month":"05"},{"model":"Ford","month":"00"},{"model":"Ford","month":"01"},{"model":"Ford","month":"03"},{"model":"Ford","month":"05"},{"model":"Ford","month":"12"}]

Expected:

[{"model":"Fiat","month":"01"},{"model":"Fiat","month":"05"}, {"model":"Fiat","month":"00"},{"model":"Ford","month":"01"},{"model":"Ford","month":"03"},{"model":"Ford","month":"05"},{"model":"Ford","month":"12"}, {"model":"Ford","month":"00"}]

3 Answers

You could special-case the 00 month in your sort comparator function:

let cars = [
  { model: 'Ford', month: '12' },
  { model: 'Ford', month: '03' }, 
  { model: 'Ford', month: '01' },
  { model: 'Ford', month: '00' },
  { model: 'Ford', month: '05' },
  { model: 'Fiat', month: '05' },
  { model: 'Fiat', month: '01' },
  { model: 'Fiat', month: '00' }
];

console.log(`Unsorted cars: ${JSON.stringify(cars)}`);

const sorted = cars.sort((a, b) =>
  a.model.localeCompare(b.model) ||
  (a.month == '00' ? 1 : (b.month == '00' ? -1 : a.month.localeCompare(b.month)))
);

console.log(`Sorted cars: ${JSON.stringify(sorted)}`);

// alternate solution without nested ternary operator
const sorted2 = cars.sort((a, b) =>
  a.model.localeCompare(b.model) ||
  a.month == '00' ||
  (b.month == '00' ? -1 : a.month.localeCompare(b.month))
);

console.log(`Sorted cars: ${JSON.stringify(sorted2)}`);

There are actually 12 months in a year. (I know right)

You can safely convert the month '00' to '13' and keep your sorting as-is

Eventually convert back '13' to '00' once sorted

This is not the best solution concerning the performance, but it's easy to set up and maintain :

let cars = [{
    model: 'Ford',
    month: '12'
  }, {
    model: 'Ford',
    month: '03'
  }, {
    model: 'Ford',
    month: '01'
  }, {
    model: 'Ford',
    month: '00'
  },
  {
    model: 'Ford',
    month: '05'
  },
  {
    model: 'Fiat',
    month: '05'
  },
  {
    model: 'Fiat',
    month: '01'
  },
  {
    model: 'Fiat',
    month: '00'
  }
];

console.log(`Unsorted cars: ${JSON.stringify(cars)}`);

// replace the '00'

const ZeroFixed = cars.map(elem => { if (elem.month === '00') elem.month = '13'; return elem });

const sorted = ZeroFixed.sort((a, b) => a.model.localeCompare(b.model) || a.month.localeCompare(b.month));

// replace back the '13'

const SortedZeroFixed = sorted.map(elem => { if (elem.month === '13') elem.month = '00'; return elem });

console.log(`Sorted cars: ${JSON.stringify(SortedZeroFixed)}`);

Definitely, the focus is on the sort compare function. You have the model and month check nailed. The challenging check is to rank "00" on the bottom. I have a number of versions of this:

  • (a.month === '00') - (b.month === '00')
  • (parseInt(a.month) === 0) - (parseInt(b.month) === 0)
  • !Number(a.month) - !Number(b.month)

Conversely, if you wanted a pattern, say, "13", to rank higher above normal sorting, you negate the rule such as:

  • (a.month !== '13') - (b.month !== '13')

When the logic is non-trivial I like to expand by repeated calculations of cmp as I work out the logic:

(a, b) => {
  let cmp = a.model.localeCompare(b.model);   // model check
  if (cmp !== 0) return cmp;
  cmp = !Number(a.month) - !Number(b.month);  // "00" check
  if (cmp !== 0) return cmp;
  cmp =  Number(a.month) -  Number(b.month);  // month check
  return cmp;
}

I usually prefer it in that form to iron out the bugs, if you want to shorten it, I do a final refactor/rewrite condensing the solution:

(a, b) =>    a.model.localeCompare(b.model)         // model check
          || !Number(a.month) - !Number(b.month)    // "00" check
          ||  Number(a.month) -  Number(b.month)    // month check

const cars = [
  { model: 'Ford', month: '12' },
  { model: 'Ford', month: '03' }, 
  { model: 'Ford', month: '01' },
  { model: 'Ford', month: '00' },
  { model: 'Ford', month: '05' },
  { model: 'Fiat', month: '05' },
  { model: 'Fiat', month: '01' },
  { model: 'Fiat', month: '00' }
];

const sorted = cars.sort((a, b) => 
      a.model.localeCompare(b.model)
  || !Number(a.month) - !Number(b.month)
  ||  Number(a.month) -  Number(b.month)
);

console.log(`Sorted cars: \n${JSON.stringify(sorted)
                            .replace(/},/g, "},\n")
                           }`);

// Sorted cars:
// [{"model":"Fiat","month":"01"},
// {"model":"Fiat","month":"05"},
// {"model":"Fiat","month":"00"},
// {"model":"Ford","month":"01"},
// {"model":"Ford","month":"03"},
// {"model":"Ford","month":"05"},
// {"model":"Ford","month":"12"},
// {"model":"Ford","month":"00"}]

Related