0

I am trying to sort this array :

var arr = ["abc", "def", "1", "6", "4", "33", "10", "]", "20", "nike", "za", "(", "ABC"]; 

and I want the result of the sort to be :

["1", "4", "6", "10", "20", "33", "ABC", "abc", "def", "nike", "za", "(", "]"] 

I tried the following code but the problem is about "10" it comes after "1". I can't resolve this problem. can anyone help ?

  • 1
    Possible duplicate of [Trying to sort array by numbers first and then letters last](https://stackoverflow.com/questions/42674567/trying-to-sort-array-by-numbers-first-and-then-letters-last) – Daniel Sep 29 '18 at 16:50
  • As they are strings the 10 will always come after the 1. Perhaps add leading 0's to pad the string, which will sort that issue out, then remove the leading 0's once sorted. – Gavin Simpson Sep 29 '18 at 16:50

3 Answers3

0

An approach with looking at each letter and a pattern for sorting with map.

var array = ["abc", "def", "1", "6", "4", "33", "10", "]", "20", "nike", "za", "(", "ABC"],
    pattern = [                    // revers sorted
        c => /^[^a-z]*$/i.test(c), // all except letters
        c => /^[a-z]*$/.test(c),   // only lower letters
        c => /^[A-Z]*$/.test(c),   // only upper letters
    ],
    result = array
        .map((value, index) => /\d/.test(value)
            ? { index, value }
            : { index, value: Array.from(value, c => pattern.map(fn => fn(c) ? c : ' ').join('')).join('') }
        )
        .sort(({ value: a }, { value: b }) => isNaN(a) - isNaN(b) || a - b || a.localeCompare(b))
        .map(el => array[el.index]);

console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Nina Scholz
  • 376,160
  • 25
  • 347
  • 392
  • thanks, your code resolved the problem of "10' , but the order is not equal to this : ["1", "4", "6", "10", "20", "33", "ABC", "abc", "def", "nike", "za", "(", "]"] – Ahmed Haiba Sep 29 '18 at 17:17
  • do you want all uppercase to appear in front, e.g. ABC, XYZ, abc, xyz? Or do you want interlaced, e.g. ABC, abc, XYZ, xyz? For the later you could incorporate a call to `.toUpperCase` and then when case-insensitive comparison is equal you invert the sort order from `localeCompare`. – Justin Kahn Sep 29 '18 at 17:22
  • thanks for the effort, now your code works perfectly. – Ahmed Haiba Sep 29 '18 at 18:21
0

This is a fairly complicated sort. You're asking that numbers be sorted low and in ascending order; that's straightforward. Everything else asks for unnatural ascending sort with non-word characters at the end and word characters sorted capital-first, which is the opposite result of localeCompare.

Here's a "brute force" solution that enumerates every possibility and handles them appropriately:

var arr = ["1", "4", "abc", "Abc", "20", "33", "ABC", "abc", "def", "nike", "za", "(", "]","1","-123", "6", "10", "20", "33", "4", "6", "10", "20", "33", "ABC", "abcd", "DEf", "nike", "za", "(", "]", "abc", "def", "nike", "za", "(", "([", "]","1", "4", "6", "10", "20", "33", "ABC", "abc", "def", "nike", "za", "](", "[]]","1", "4", "6", "10", "20", "10","%","$", "ABC", "33", "XYZ", "abc", "def", "nike", "za", "(", "]", "a"];

arr.sort((a, b) => {
  if (a.match(/^-?\d+$/)) {
    return b.match(/^-?\d+$/) ? (+a) - (+b) : -1;
  }
  else if (a.match(/^\w+$/)) {
    if (b.match(/^-?\d+$/)) {
      return 1;
    }
    else if (b.match(/^\w+$/)) {
      if (a.match(/^[A-Z]/)) {
        if (b.match(/^[A-Z]/)) {
          const aLen = a.match(/^[A-Z]+/)[0].length;
          const bLen = b.match(/^[A-Z]+/)[0].length;
          
          if (aLen !== bLen) {
            return bLen - aLen;
          }

          return a.localeCompare(b);
        }
        
        return -1;
      }
      
      return b.match(/^[A-Z]/) ? 1 : a.localeCompare(b);
    }
    
    return -1;
  }
  else if (b.match(/^-?\d+$/) || b.match(/^\w+$/)) {
    return 1;
  }
  
  return a.localeCompare(b);  
});

console.log(arr);
ggorlen
  • 44,755
  • 7
  • 76
  • 106
0

It seems like you can solve most of this with the options on localeCompare. To sort the non-alphanumerics last you can add a simple regex to the sort:

var arr = ["abc", "def", "1", "6", "4", "33", "10", "]", "20", "nike", "za", "(", "ABC"]; 

let re = new RegExp('[0-9a-z]', 'i')
arr.sort((a,b) =>  
     (re.test(b) - re.test(a)) 
     || a.localeCompare(b, 'en', {numeric: true, caseFirst:"upper"}))

console.log(arr)
Mark
  • 90,562
  • 7
  • 108
  • 148
  • Beautiful answer which I want to upvote, but if you run this with the extended test case in my answer, this sort fails on upper/lower casing. I'm not sure how precise OP wishes to be here. – ggorlen Sep 29 '18 at 23:09