Aaron C.
Aaron C.

Reputation: 91

Sorting a string array with uppercase first including accents

So I was wandering around the internet searching for some sorting function in js. Here is the problem. We have a string array like this :

['único', 'UNICO', 'árbol', 'ARBOL', 'cosas', 'COSAS', 'fútbol', 'FUTBOL']

and we want somthing like this (Uppercase first):

['ARBOL', 'COSAS', 'FUTBOL', 'UNICO', 'árbol', 'cosas', 'fútbol', 'único']

or like this (lowercase first):

['árbol', 'cosas', 'fútbol', 'único', 'ARBOL', 'COSAS', 'FUTBOL', 'UNICO']

The thing is : it's really easy to get this :

['ARBOL', 'COSAS', 'FUTBOL', 'UNICO', 'cosas', 'fútbol', 'árbol','único']

with the .sort(); function but we don't want the accentuated words at the end so we use the

.sort(function(a, b) {
  return a.localCompare(b);
});

but we end up with this ...

['ARBOL', 'árbol', 'COSAS', 'cosas', 'FUTBOL', 'fútbol', 'UNICO', 'único']

Do you guys have any idea on how to combine both ?

Upvotes: 7

Views: 4064

Answers (5)

Nina Scholz
Nina Scholz

Reputation: 386654

You could take a sorting with map and replace every character with two characters, depending on the case with a space in front or behind of the caracter. Then sort and map a new array.

Example

unsorted

index  value
-----  --------------------
   0   'ú   n  i  c  o '
   1   '  U  N  I  C  O'
   2   'á   r  b  o  l '
   3   '  A  R  B  O  L'
   4   ' c  o  s  a  s '
   5   '  C  O  S  A  S'
   6   ' f ú   t  b  o  l '
   7   '  F  U  T  B  O  L'

sorted

index  value
-----  --------------------
   3   '  A  R  B  O  L'
   5   '  C  O  S  A  S'
   7   '  F  U  T  B  O  L'
   1   '  U  N  I  C  O'
   4   ' c  o  s  a  s '
   6   ' f ú   t  b  o  l '
   2   'á   r  b  o  l '
   0   'ú   n  i  c  o '

var array = ['único', 'UNICO', 'árbol', 'ARBOL', 'cosas', 'COSAS', 'fútbol', 'FUTBOL'],
    result = array
        .map((s, index) => ({ index, value: Array.from(s, c => c === c.toUpperCase()
            ? '  ' + c
            : c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z'
                ? ' ' + c + ' '
                : c + '  '
        ).join('') }))
        .sort((a, b) => a.value.localeCompare(b.value))
        .map(({ index }) => array[index]);

console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Upvotes: 0

Ori Drori
Ori Drori

Reputation: 191996

Sort the array, and if:

  1. a are both uppercase or both lowercase, sort them via localeCompare.
  2. if only a is uppercase a should be 1st (return -1).
  3. the default (only b is uppercase) is b first (return 1).

Note: this assumes that all words don't have mixed uppercase and lowercase letters.

const arr = ['único', 'UNICO', 'árbol', 'ARBOL', 'cosas', 'COSAS', 'fútbol', 'FUTBOL']

arr.sort((a, b) => {
  const aIsUpperCase = a.toUpperCase() === a;
  const bIsUpperCase = b.toUpperCase() === b;
  
  if(aIsUpperCase === bIsUpperCase) return a.localeCompare(b, 'es');
  
  if(aIsUpperCase) return -1;
  
  return 1;
})

console.log(arr)

Upvotes: 2

baao
baao

Reputation: 73241

You could split them by case, sort each and put back together.

let foo = ['único', 'UNICO', 'árbol', 'ARBOL', 'cosas', 'COSAS', 'fútbol', 'FUTBOL'];
foo = foo.reduce((a, b) => {
    b.toUpperCase() === b ? a[0].push(b) : a[1].push(b);
    return a;
}, [[],[]]).flatMap(e => e.sort((a, b) => a.localeCompare(b)));

console.log(foo);

Upvotes: 0

fjc
fjc

Reputation: 5815

You can simply extend the sort function to prioritize uppercase characters in the beginning of strings:

const arr = ['ÁRBOL', 'único', 'UNICO', 'árbol', 'ARBOL', 'cosas', 'COSAS', 'fútbol', 'FUTBOL'];

function startsWithUppercase(str) {
    return str.substr(0, 1).match(/[A-Z\u00C0-\u00DC]/);
}

arr.sort(function(a, b) {
    if (startsWithUppercase(a) && !startsWithUppercase(b)) {
        return -1;
    } else if (startsWithUppercase(b) && !startsWithUppercase(a)) {
        return 1;
    }
    return a.localeCompare(b);
});

console.log(arr);

Upvotes: 5

Snow
Snow

Reputation: 4097

I don't believe it's possible with localeCompare alone, see:

How to get localeCompare to behave similarly to .sort(), so that all capital letters come first?:

But you can combine the method described here with sort:

const arr = ['único', 'UNICO', 'árbol', 'ARBOL', 'cosas', 'COSAS', 'fútbol', 'FUTBOL'];
const norm = str => str.normalize('NFD').replace(/[\u0300-\u036f]/g, "")
arr.sort((a, b) => Number(norm(a) > norm(b)) || -(Number(norm(b) > norm(a))));
console.log(arr);
// ['ARBOL', 'COSAS', FUTBOL', 'UNICO', 'árbol', 'cosas', 'fútbol', 'único']

Upvotes: 3

Related Questions