JavaScript custom .sort() of double array object using .localeCompare() not working

140 Views Asked by At

I was working on a solution to a leetcode problem (https://leetcode.com/problems/sender-with-largest-word-count/) when I hit a scenario I spent hours trying to debug and still can't figure out what's going on. To simplify, given an object:

let items = [
  [ 'K', 4 ],           [ 'kFIbpoFxn', 10 ],  [ 'yErgn', 6 ],
  [ 'N', 8 ],           [ 'wtJesr', 9 ],      [ 'rusffeL', 14 ],
  [ 'KlpoodEd', 5 ],    [ 'qGcQqIVdFr', 1 ],  [ 'ztmCdK', 10 ],
  [ 'HFILjKln', 12 ],   [ 'TmmQZ', 18 ],      [ 'R', 7 ],
  [ 'CNh', 7 ],         [ 'YMQDBkOWy', 22 ],  [ 'kjiSc', 4 ],
  [ 'cGMsZxxx', 6 ],    [ 'PPqsmNBewN', 21 ], [ 'gbtn', 10 ],
  [ 'nQNcL', 8 ],       [ 'rK', 10 ],         [ 'ppr', 16 ],
  [ 'LhSVp', 11 ],      [ 'Ub', 7 ],          [ 'QGRFMLY', 11 ],
  [ 'SdDObYkD', 9 ],    [ 'q', 3 ],           [ 'suAakSCuHz', 6 ],
  [ 'dnzhjdwrEt', 8 ],  [ 'ubIEXAO', 22 ],    [ 'EsBuLal', 11 ],
  [ 'xlQqQRrdTv', 12 ], [ 'mWxCG', 8 ],       [ 'DmwIEmS', 5 ],
  [ 'nBQLLS', 6 ],      [ 'QhF', 4 ],         [ 'bmtYQKYv', 5 ],
  [ 'PRiNk', 7 ],       [ 'QyYJw', 7 ],       [ 'QIFauTN', 3 ],
  [ 'zJLcUq', 13 ],     [ 'TU', 1 ],          [ 'lCkGjDY', 7 ],
  [ 'A', 6 ]
]

I am attempting to sort it based first on highest integer, but if those values are equal, by largest lexicographical value, ie the .reverse() of how JS sorts strings by default

my sorting code is:

items.sort(function(a, b) {
  return b[1] !== a[1] ? b[1] - a[1] : -a[0].localeCompare(b[0]);
});

resulting in:

[
  [ 'YMQDBkOWy', 22 ], [ 'ubIEXAO', 22 ],    [ 'PPqsmNBewN', 21 ],
  [ 'TmmQZ', 18 ],     [ 'ppr', 16 ],        [ 'rusffeL', 14 ],
  [ 'zJLcUq', 13 ],    [ 'xlQqQRrdTv', 12 ], [ 'HFILjKln', 12 ],
  [ 'QGRFMLY', 11 ],   [ 'LhSVp', 11 ],      [ 'EsBuLal', 11 ],
  [ 'ztmCdK', 10 ],    [ 'rK', 10 ],         [ 'kFIbpoFxn', 10 ],
  [ 'gbtn', 10 ],      [ 'wtJesr', 9 ],      [ 'SdDObYkD', 9 ],
  [ 'nQNcL', 8 ],      [ 'N', 8 ],           [ 'mWxCG', 8 ],
  [ 'dnzhjdwrEt', 8 ], [ 'Ub', 7 ],          [ 'R', 7 ],
  [ 'QyYJw', 7 ],      [ 'PRiNk', 7 ],       [ 'lCkGjDY', 7 ],
  [ 'CNh', 7 ],        [ 'yErgn', 6 ],       [ 'suAakSCuHz', 6 ],
  [ 'nBQLLS', 6 ],     [ 'cGMsZxxx', 6 ],    [ 'A', 6 ],
  [ 'KlpoodEd', 5 ],   [ 'DmwIEmS', 5 ],     [ 'bmtYQKYv', 5 ],
  [ 'QhF', 4 ],        [ 'kjiSc', 4 ],       [ 'K', 4 ],
  [ 'QIFauTN', 3 ],    [ 'q', 3 ],           [ 'TU', 1 ],
  [ 'qGcQqIVdFr', 1 ]
]

the issue is 'ubIEXAO' should be sorted ahead of 'YMQDBkOWy'. when you do the isolated -a.compareLocale(b) it works correctly***, and in other test cases this part of the code correctly sorts other strings. but not in this test case. what am I missing? thank you!

(I know there are myriad other ways to solve this problem, I just want to know why this sort function isn't working as intended for this object)

***UPDATE from reading some of the comments I just realized that I was only half testing this. Initially I checked for what value the -localeCompare returned and then hardcoded that value into a sort to verify the output. As it turns out, for some reason, hardcoding a -1 returns a different result than the -localeCompare function itself, which also console logs a value of -1. I do not understand this interaction!

let a = 'YMQDBkOWy';
let b = 'ubIEXAO';
console.log('localeCompare test: ' + -a.localeCompare(b))
let c = [a,b]
console.log(c.sort( (a,b) => {console.log('1'); return -a.localeCompare(b)}))
c = [a,b]
console.log(c.sort( (a,b) => {return -1}))

3

There are 3 best solutions below

16
Unmitigated On

You can directly use the comparison operators to make it case sensitive.

items.sort((a, b) => b[1] - a[1] || (b[0] > a[0]) - (b[0] < a[0]));
2
Liap On

It is because of the order of compareFn(a, b) value.

a is the next item and b is the current item.

So in this case the return result is equal to 1, which is > 0 so it will

sort a after b, e.g. [b, a]. Sort order

which is mean b will be placed before a ~ [ 'YMQDBkOWy', 22 ] before [ 'ubIEXAO', 22 ]

a = [ 'ubIEXAO', 22 ]
b = [ 'YMQDBkOWy', 22 ]

-a[0].localeCompare(b[0]) =  1

let items = [
  ["YMQDBkOWy", 22],
  ["ubIEXAO", 22],
];

items.sort(function (a, b) {
  console.log("a is second item", a);
  console.log("b is first item", b);

  console.log("-a[0].localeCompare(b[0]) = ", -a[0].localeCompare(b[0])); // 1

  return -a[0].localeCompare(b[0]); // 1  > 0
});

2
PeterKA On
  • the issue is 'ubIEXAO' should be sorted ahead of 'YMQDBkOWy'. when you do the isolated -a.compareLocale(b) it works correctly. In my case, the sort order is the same hence no discrepancy, per the code snippet below.

const sample = [ 'YMQDBkOWy', 'LeMoN', 'yMQDBkOWy', 'ubIEXAO', 'UbIEXAO' ];

sample.sort( (a,b) => -a.localeCompare(b) );

console.log( sample );

The output is [ "YMQDBkOWy", "yMQDBkOWy", "UbIEXAO", "ubIEXAO", "LeMoN" ] which shows the two strings in the same order as the array of arrays: "YMQDBkOWy", "ubIEXAO".