2

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?

HoldOffHunger
  • 18,769
  • 10
  • 104
  • 133
Razvan
  • 710
  • 3
  • 9
  • 25
  • possible duplicate of [Sort JavaScript String Array containing numbers](http://stackoverflow.com/questions/3108530/sort-javascript-string-array-containing-numbers) – hsz Jan 30 '13 at 08:19
  • see `natural sort of text and numbers, JavaScript` on http://stackoverflow.com/questions/2802341/natural-sort-of-text-and-numbers-javascript – Adriano Sep 26 '14 at 11:38
  • 1
    Possible duplicate of [Javascript : natural sort of alphanumerical strings](https://stackoverflow.com/questions/2802341/javascript-natural-sort-of-alphanumerical-strings) – Stephen Quan Sep 12 '17 at 22:20

4 Answers4

17

Try like this:

arr = arr.sort(function(a, b) {
  return +/\d+/.exec(a)[0] - +/\d+/.exec(b)[0];
});

Edit: Fixed it works now, it had a couple errors: http://jsbin.com/iwejik/1/edit

elclanrs
  • 92,861
  • 21
  • 134
  • 171
  • Yup, you were right, I was too fast to post, forgot to cast number and grab multiple numbers, that was the issue mainly. – elclanrs Jan 30 '13 at 08:28
  • 1
    Instead of `>` it should be `-`. Please check the description of https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort. – kennytm Oct 01 '13 at 14:12
  • 5
    I believe it crashes when you don't have any numbers in a or b – Gerard Mar 21 '14 at 14:46
  • works like a charm even if numbers and letters are in different positions – ericjam Mar 30 '14 at 03:47
  • 1
    Your example on jsbin doesn't work for me at all. I got warnings about the backslash before the "d"s. However, I copied the code to JSFiddle and it worked fine. Also, while your answer technically answers the asker's question, it doesn't work at all if the array items don't contain digits. – Mr. Lance E Sloan Apr 23 '14 at 18:49
  • TypeError: /\d+/.exec(...) is null – HoldOffHunger Sep 14 '17 at 14:52
3

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.

Mac
  • 1,432
  • 21
  • 27
2
columnArray.sort(function(a,b) {
     return parseInt(a.match(/\d+/)[0],10) - parseInt(b.match(/\d+/)[0],10);
});

demo

Engineer
  • 47,849
  • 12
  • 88
  • 91
  • As Lance observed with the accepted answer, "while your answer technically answers the asker's question, it doesn't work at all if the array items don't contain digits." – Mac Mar 27 '22 at 13:46
2

The selected solution is not quite accurate, you can catch error if your array contains elements without numbers:

TypeError: null is not an object (evaluating '/\d+/.exec(b)[0]')

This function will also properly sort strings like "1A" and "1B":

var arr = ["house11b", "house2", "house", "house10", "house1", "house11a"];
const natural = (a, b) => {
    var an = (/\d+/.exec(a) || [''])[0];
    var bn = (/\d+/.exec(b) || [''])[0];
    return an == bn ? (a > b ? 1 : -1) : an - bn;
}

console.log(
    arr.sort((a, b) => natural(a, b))
);
Denis
  • 302
  • 4
  • 7
  • 1
    Much better, but fails to accurately sort when the string contains no number, e.g. this fails: `["house11b", "house2", "house10", "house1", "house", "house11a"];` – Mac Mar 25 '22 at 18:43
  • 1
    Thank you, I've improved the function. – Denis Mar 26 '22 at 19:13