113

W3CSchools has this example:

var fruits = ["Banana", "Orange", "Apple", "Mango"];
fruits.sort();
fruits.reverse();

Is this the most efficient way to sort strings in descending order in Javascript?

Update

One of the answers is using localeCompare. Just curious whether if we do reverse(), will that work for all locales (Maybe this is a separate question - Just let me know in the comments)?

Ole
  • 41,793
  • 59
  • 191
  • 359
  • 1
    By what measure of efficiency? – David Thomas Aug 26 '18 at 20:42
  • Possible duplicate https://stackoverflow.com/questions/1063007/how-to-sort-an-array-of-integers-correctly – Asons Aug 26 '18 at 20:49
  • `.sort()` and `.reverse()` is already the most efficient way. – Sebastian Simon Aug 26 '18 at 20:52
  • 1
    `.sort((a, b) => -(a>b)||+(a – Bergi Aug 26 '18 at 20:53
  • `reverse()` doesn't care about the locales, it only modifies the indexes of the array in reverse order – colxi Aug 26 '18 at 21:25
  • as I said in my answer using just `sort` and `reverse` a > Z and Á > Z, that is completely wrong. Try this `["a","b","c","A","B","Z"]`, the result is `[ 'c', 'b', 'a', 'Z', 'B', 'A' ]` : / – Emeeus Aug 26 '18 at 21:45
  • So would it be fair to say that we should use reverse when all the characters are lower case, and localeCompare otherwise? – Ole Aug 27 '18 at 15:11
  • So for example API wise we may have two methods `sortLowercaseDescending()` and `sortStringDescending()` ... – Ole Aug 27 '18 at 15:28
  • Provided a typescript sorting implementation here (If anyone has any improvement feedback that would be great): https://github.com/fireflysemantics/collections/blob/master/src/index.ts – Ole Aug 28 '18 at 00:51

5 Answers5

196

If you consider

obj.sort().reverse();

VS

obj.sort((a, b) => (a > b ? -1 : 1))

VS

obj.sort((a, b) => b.localeCompare(a) )

The performance winner is : obj.sort().reverse().

Testing with an array of 10.000 elements, obj.sort().reverse() is faster than obj.sort( function ) (except on chrome), and obj.sort( function ) (using localCompare).

Performance test here :

var results = [[],[],[]]

for(let i = 0; i < 100; i++){
  const randomArrayGen = () => Array.from({length: 10000}, () => Math.random().toString(30));
  const randomArray = randomArrayGen();
  const copyArray = x => x.slice();

  obj = copyArray(randomArray);
  let t0 = performance.now();
  obj.sort().reverse();
  let t1 = performance.now();

  obj = copyArray(randomArray);
  let t2 = performance.now();
  obj.sort((a, b) => (a > b ? -1 : 1))
  let t3 = performance.now();

  obj = copyArray(randomArray);
  let t4 = performance.now();
  obj.sort((a, b) => b.localeCompare(a))
  let t5 = performance.now();  

  results[0].push(t1 - t0);
  results[1].push(t3 - t2);
  results[2].push(t5 - t4);  
}

const calculateAverage = x => x.reduce((a,b) => a + b) / x.length ;

console.log("obj.sort().reverse():                   " + calculateAverage(results[0]));
console.log("obj.sort((a, b) => (a > b ? -1 : 1)):   " + calculateAverage(results[1]));
console.log("obj.sort((a, b) => b.localeCompare(a)): " + calculateAverage(results[2]));
Patrick
  • 674
  • 1
  • 8
  • 22
colxi
  • 7,640
  • 2
  • 45
  • 43
  • How does that compare to using `localeCompare`? – Ole Aug 26 '18 at 21:02
  • 1
    i uodated my answer and the jsperf, to include the `localCompare` case – colxi Aug 26 '18 at 21:10
  • Wow - That was impressive! – Ole Aug 26 '18 at 21:11
  • 1
    the same situation woud be `(a,b)=>b.localeCompare(a))` not `b.localeCompare(a, 'es', {sensitivity: 'base'}))` that is for special characters. `obj.sort().reverse()` doesn't work with special characters – Emeeus Aug 26 '18 at 21:14
  • 5
    [`(a, b) => (a > b ? -1 : 1)` is not enough](https://stackoverflow.com/a/20892652/1048572) – Bergi Aug 27 '18 at 15:28
  • 1
    Node benchmarks on my machine show #2 as being the fastest: `obj.sort().reverse(): 3.090556930010207` `obj.sort((a, b) => (a > b ? -1 : 1)): 2.7984550699871034` `obj.sort((a, b) => b.localeCompare(a)): 10.975987620060332` –  Jun 15 '20 at 02:50
  • yeah, sort+reverse r both native binary code, while sort+lambda comes with lambda is js code – Dee Dec 03 '20 at 03:32
  • `obj.sort((a, b) => (a > b ? -1 : 1))` is both incorrect and slow. Correct version is `obj.sort((a, b) => b - a)` which easily beats #1. – Nikola Mihajlović Nov 06 '21 at 03:35
  • 1
    `obj.sort((a, b) => b - a)` only works for numbers, not strings! – Doin Jan 19 '22 at 11:43
7

Using just sort and reverse a > Z , that is wrong if you want to order lower cases and upper cases strings:

var arr = ["a","b","c","A","B","Z"];

arr.sort().reverse();

console.log(arr)//<-- [ 'c', 'b', 'a', 'Z', 'B', 'A' ] wrong!!!

English characters

var arr = ["a","b","c","A","B","Z"];

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

console.log(arr)

Special characters using locales, in this example es (spanish)

var arr = ["a", "á", "b","c","A","Á","B","Z"];

arr.sort((a, b) => b.localeCompare(a, 'es', {sensitivity: 'base'}))


console.log(arr)

sensitivity in this case is base:

Only strings that differ in base letters compare as unequal. Examples: a ≠ b, a = á, a = A.

Emeeus
  • 5,072
  • 2
  • 25
  • 37
  • 2
    What makes you think it is wrong? OP asked to make the sorting descending, not to make it case-invariant. – Bergi Aug 27 '18 at 15:25
  • 2
    @Bergi Using `sort` and `reverse` could solve the problem that OP is facing using that specific set of data, it is not good as a general solution in my opinion. Also OP said in the title `strings`, not certain kind of strings. Either way "is wrong" is a bad generalization. – Emeeus Aug 27 '18 at 15:45
2

The easiest way to revers the order of sorting is by swapping the operands. In ES2015 that's as easy as [b, a] = [a, b]. A full example:

function compareWithOrder(a, b, shouldReverse = false) {
  if (shouldReverse) {
    [b, a] = [a, b]
  }
  return yourComparatorFn(a, b)
}
Nick Ribal
  • 1,959
  • 19
  • 26
0

var arr = ["a","b","c","A","B","Z"];

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

console.log(arr)
0

I know this is an old question, but an interesting one. This is my solution for a non-special character's input.

var arr = ["a","b","c","A","B","Z"];

  console.log(arr.sort((a,b)=> {
      const lastCodeIn = b.toLowerCase().charCodeAt();
      const lastCode = b.charCodeAt();
      const firstCodeIn = a.toLowerCase().charCodeAt();
      const firstCode = a.charCodeAt();

      if(lastCodeIn - firstCodeIn === 0){
        return lastCode - firstCode;
      }
      return lastCodeIn - firstCodeIn;
    })
  );//[ 'Z', 'c', 'b', 'B', 'a', 'A' ]

The reason is that ascii code for UPPER case are lower than lower case.