Search code examples
javascriptarrayssortingnatural-sort

javascript natural sort


I have this array :

var columnArray =
['columnNumber1','columnNumber6','coulmnNumber7','columnNumber11','columnNumber12'];

If I do columnArray.sort();, it gives me :

columnArray:
['columnNumber1','columnNumber11','coulmnNumber12','columnNumber6','columnNumber7']

How can I sort it correctly?


Solution

  • The solution is to use localeCompare(), the options of which are here.

    Now we can sort this array

    const items = ['3rd', 'Apple', '24th', '99 in the shade', 'Dec3', 'Dec', 'Dec20', '10000', 'house', 'house11b', 'house11a', 'house99', '101', '$1.23'];
    

    with one line of code

    items.sort( ( a, b ) => a.localeCompare( b, navigator.languages[ 0 ] || navigator.language, { numeric: true, ignorePunctuation: true } ) );
    

    yielding

    ['$1.23', '3rd', '24th', '99 in the shade', '101', '10000', 'Apple', 'Dec', 'Dec3', 'Dec20', 'house', 'house11a', 'house11b', 'house99']
    

    Credit for this elegant solution belongs here.

    EDIT

    I'm not satisfied. This works, but only for strings. I'd like to have a sort that will chew on numbers and arrays too. I really want a one-size-fits-all, if I can get it. With that in mind, this update

    items.sort( ( a, b ) => ( a + '' ).localeCompare( b, navigator.languages[ 0 ] || navigator.language, { numeric: true, ignorePunctuation: true } ) );
    

    using this updated array (containing strings, integers, decimals and a nested array ['a',1,2])

    let items = ['3rd', 'Apple', .99, '24th', 'apple.sauce', '99 in the shade', 99, 'Dec3', 'B', 'a', 'Dec', 'Dec20', 12, '10000', 'house', 1.24, '1.22', 'house11b', 1, 'house11a', 'house99', '101', 400.23, '$1.23', ['a',1,2]];
    

    gets us closer with

    ['$1.23', 0.99, 1, '1.22', 1.24, '3rd', 12, '24th', 99, '99 in the shade', '101', 400.23, '10000', 'a', Array(3), 'Apple', 'apple.sauce', 'B', 'Dec', 'Dec3', 'Dec20', 'house', 'house11a', 'house11b', 'house99']
    

    LASTLY

    Now, to satisfy my own OCD tendencies, I want the currency value $1.23 to line up with the rest of the decimal values. Since indulging OCD often comes at a cost, I'm using this ugly ternary:

    ( a + '' ).substr( 0, 1 ) === '$' ? ( a + '' ).substr( 1, ( a + '' ).length ) : ( a + '' )
    

    Also, I'll specify the language instead of grabbing it from the browser and set base sensitivity (which is language dependent).

    So a case insensitive sort ignorePunctuation: true that treats strings as numbers (as much as possible) numeric: true, and respects the order of graphemes in the language sensitivity: 'base' as well as sorts USD currency as decimals, is this

    items.sort( ( a, b ) => ( ( a + '' ).substr( 0, 1 ) === '$' ? ( a + '' ).substr( 1, ( a + '' ).length ) : ( a + '' ) ).localeCompare( b, 'en', { ignorePunctuation: true, numeric: true, sensitivity: 'base' } ) );
    

    which gives us this

    [0.99, 1, '1.22', '$1.23', 1.24, '3rd', 12, '24th', 99, '99 in the shade', '101', 400.23, '10000', 'a', Array(3), 'Apple', 'apple.sauce', 'B', 'Dec', 'Dec3', 'Dec20', 'house', 'house11a', 'house11b', 'house99']
    

    which is as close to a perfect one-size-fits-all as I need it to be. 👍