Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to change sorting order when sorting mixed items with String.localeCompare?

Let's say we have an array of objects with mixed name values (nums, cyrillic, english):

(If the code doesn't work for you, change undefined to 'ru', it will also change the sorting structure)

let ascending = true

var items = [
  {name: 'c', value: ''}, 
  {name: 'b', value: ''}, 
  {name: 'a', value: ''}, 
  {name: 'д', value: ''}, 
  {name: 'в', value: ''}, 
  {name: '41', value: ''}, 
  {name: 'а', value: ''}, 
  {name: 'б', value: ''}, 
  {name: '0', value: ''}, 
  {name: '31', value: ''}, 
  {name: '4', value: ''}, 
  {name: 'г', value: ''}
]

items.sort(function (a, b) {
  // ascending order
  if (ascending) {
    return a.name.localeCompare(b.name, undefined, { numeric: true });
  }
  // descending order
  else {
    return b.name.localeCompare(a.name, undefined, { numeric: true });
  }
})

console.log(items);

Current result

I get the sorted array in ascending order with the following structure:

  • Nums (ascending)
  • English (ascending)
  • Non-english (ascending)

Result when ascending = true:

{name: "0", value: ""}
{name: "31", value: ""}
{name: "4", value: ""}
{name: "41", value: ""}
{name: "a", value: ""}
{name: "b", value: ""}
{name: "c", value: ""}
{name: "а", value: ""}
{name: "б", value: ""}
{name: "в", value: ""}
{name: "г", value: ""}
{name: "д", value: ""}

Desired result

I need it to be able to sort the array in descending order when ascending = false and retain the structure:

  • Nums (descending)
  • English (descending)
  • Non-english (descending)

Needed result when ascending = false:

{name: "41", value: ""}
{name: "31", value: ""}
{name: "4", value: ""}
{name: "0", value: ""}    
{name: "c", value: ""}
{name: "b", value: ""}
{name: "a", value: ""}
{name: "д", value: ""}
{name: "г", value: ""}
{name: "в", value: ""}
{name: "б", value: ""}
{name: "а", value: ""}

Problem

When I change ascending = false and change positions of b.name with a.name it just flips the whole array upside down instead of flipping the values in their "category" (nums, english, cyrillic).

I'm not sure how to do it properly. I mean, reversing the array reverses the values, so should I just restructure the array "categories" after it flipped the array? Something like this maybe:

  • get all the objects with numbers with isNaN() and move them on top

  • then get items containing only a-z at [0] and move them below the numbers "category"

  • everything else just stays on the bottom

like image 591
Un1 Avatar asked Jul 11 '18 13:07

Un1


Video Answer


1 Answers

If you are have a mechanism to identify numbers, english and non-english strings you can use the following idea:

var items = [
  { name: "c", value: "" },
  { name: "b", value: "" },
  { name: "a", value: "" },
  { name: "д", value: "" },
  { name: "в", value: "" },
  { name: "41", value: "" },
  { name: "а", value: "" },
  { name: "б", value: "" },
  { name: "0", value: "" },
  { name: "31", value: "" },
  { name: "4", value: "" },
  { name: "г", value: "" }
];

function sortFunctionMaker(ascending) {
  function isNumber(str) {
    return Number.isNaN(Number(str)) === false;
  }

  function isEnglish(str) {
    return /^[a-zA-Z]+$/.test(str);
  }

  return function(a, b) {
    var aw, bw;

    if (isNumber(a.name)) {
      aw = 1;
    } else if (isEnglish(a.name)) {
      aw = 2;
    } else {
      aw = 3;
    }
    if (isNumber(b.name)) {
      bw = 1;
    } else if (isEnglish(b.name)) {
      bw = 2;
    } else {
      bw = 3;
    }

    if (aw !== bw) {
      // a and b belong to different categories
      // no further comparison is needed
      return aw - bw;
    } else if (aw === 1) {
      // both are numbers
      // sort mathematically
      return (ascending ? 1 : -1) * (a.name - b.name);
    } else {
      // both are english or otherwise
      // sort using localeCompare
      return (ascending ? 1 : -1) * a.name.localeCompare(b.name);
    }
  }
}

items.sort(sortFunctionMaker(true));
console.log("Ascending");
items.forEach(function(item) {
  console.log(item.name);
});

items.sort(sortFunctionMaker(false));
console.log("Descending");
items.forEach(function(item) {
  console.log(item.name);
});
console.groupEnd();
like image 114
Salman A Avatar answered Nov 01 '22 00:11

Salman A