93

I have a mixed array that I need to sort by alphabet and then by digit

[A1, A10, A11, A12, A2, A3, A4, B10, B2, F1, F12, F3]

How do I sort it to be:

[A1, A2, A3, A4, A10, A11, A12, B2, B10, F1, F3, F12]

I have tried

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

but that only sorts it alphabetically. Can this be done with either straight JavaScript or jQuery?

Mel
  • 5,837
  • 10
  • 37
  • 42
solefald
  • 1,739
  • 1
  • 15
  • 29
  • Are the numerical values always at the end of the string? – Orbling Dec 02 '10 at 21:47
  • 2
    Possible duplicate of [How to sort strings in JavaScript](https://stackoverflow.com/questions/51165/how-to-sort-strings-in-javascript) – feeela Aug 28 '18 at 11:35

18 Answers18

119
const sortAlphaNum = (a, b) => a.localeCompare(b, 'en', { numeric: true })

Usage:

const sortAlphaNum = (a, b) => a.localeCompare(b, 'en', { numeric: true })
console.log(['A1', 'A10', 'A11', 'A12', 'A2', 'A3', 'A4', 'B10', 'B2', 'F1', 'F12', 'F3'].sort(sortAlphaNum))

Gives:

["A1", "A2", "A3", "A4", "A10", "A11", "A12", "B2", "B10", "F1", "F3", "F12"]

You may have to change the 'en' argument to your locale or determine programatically but this works for english strings.

localeCompare is supported by IE11, Chrome, Firefox, Edge and Safari 10.

Jon Wyatt
  • 1,786
  • 1
  • 11
  • 16
  • This should be the accepted answer IMO. Small nit: The first line contains a trailing backtick after { numeric: true }) – w3dot0 Mar 01 '21 at 15:11
111

var reA = /[^a-zA-Z]/g;
var reN = /[^0-9]/g;

function sortAlphaNum(a, b) {
  var aA = a.replace(reA, "");
  var bA = b.replace(reA, "");
  if (aA === bA) {
    var aN = parseInt(a.replace(reN, ""), 10);
    var bN = parseInt(b.replace(reN, ""), 10);
    return aN === bN ? 0 : aN > bN ? 1 : -1;
  } else {
    return aA > bA ? 1 : -1;
  }
}
console.log(
["A1", "A10", "A11", "A12", "A2", "A3", "A4", "B10", "B2", "F1", "F12", "F3"].sort(sortAlphaNum)
)
Luca Kiebel
  • 9,790
  • 7
  • 29
  • 44
epascarello
  • 204,599
  • 20
  • 195
  • 236
  • Just beat me too it, only modification I would suggest, given the ordering of the tests, the regexp should also be ordered via addition of `^` and `$` front and back respectively on each. – Orbling Dec 02 '10 at 21:56
  • 5
    So catching up a little late...but you don't need the `else` block since the first `if` will `return` if `aA === bA` – phatskat Jan 20 '14 at 16:40
  • How come we didn't use the `i` flag? Is `/[^a-zA-Z]/g` more performant than `/[^a-z]/ig`? – Noitidart Dec 18 '15 at 14:25
  • 2
    @Noitidart preference. There should be no difference between the two. – epascarello Dec 18 '15 at 14:37
  • 5
    This is a good answer but it very badly needs comments. It took me awhile to read this and for it to make sense. – zfrisch May 30 '17 at 21:16
  • This apparently only works for this example. Trying to sort `[ 'Alpha 3', 'Alpha', 'Alpha 1' ]` fails badly! [example on repl.it](https://repl.it/KvUp/0). David Koelle has a [version](http://www.davekoelle.com/files/alphanum.js) that works in most cases. – Davorin Sep 11 '17 at 11:01
  • @Davorin if you look at the regular expression you would see it does NOT match whitespace. Since the original question was only A-Z and 0-9, there was no need to support those characters. You could easily change the regular expressions to include whitespace. – epascarello Sep 11 '17 at 12:14
  • If you have values with decimals you can add \. to the reN regex: `var reN = /[^0-9\.]/g` – Koen Dec 26 '17 at 11:27
  • You call the function sortAlphaNum without any parameters but the function takes in two parameters (a,b) .. How does this work? – emarel Dec 27 '17 at 22:53
  • regex vars shouldnt be globals, should be inside the function expression ... the nested ternary is a bit of a headache but if you get off on that js golf thats cool, just not a solution that reads quickly – heug Jul 24 '18 at 19:11
  • @heug Thanks for your opinions! – epascarello Jul 24 '18 at 19:15
  • @zfrisch Exactly! It works but it would be nice to know how this function actually works. – Anish Sana Jun 17 '19 at 14:29
  • @AnishSana what do you not understand? Seems basic enough. Matches the string first, than if it equals, matches numbers, and sorts based on that.... – epascarello Jun 17 '19 at 15:24
  • @epascarello I understood how it works. I don't usually use JavaScript so had to print out a few statements to figure it out. Just saying that it would be helpful to use a few comments explaining it, since this is the best answer here and will likely be copy-pasted wildly. – Anish Sana Jun 17 '19 at 17:40
  • 2
    @epascarello I really appreciated it when I found it - it's just a little perplexing when you first view it. It's succinct, but not thematically named, and if you're not familiar with regular expressions, ternary, or general sorting it's a bit of a leap to understand. Alphanumeric sorting is a pretty common question, and the ramp up to asking it doesn't require more than a cursory knowledge of arrays, so assuming more than that is going to get people asking for commentary. It's a good answer and your prerogative, but a description would likely make it easier to digest for everyone interested. – zfrisch Jun 17 '19 at 19:48
  • 1
    no offense of course, because it did really help me. +1. – zfrisch Jun 17 '19 at 19:49
  • My array like this ["1", "12", "13","2", "4", "19A","19B",'11A',"12A" ,"12", "3", "4","3","A1", "10A", "11A", "12B", "2A", "3A", "4C", "10B","12A", "2B", "ABC", "A", "3F","1AC","1AB"] How to sort this array – Maulik Santoki Nov 22 '19 at 13:30
  • @MaulikSantoki reverse the logic. `function sortAlphaNum(a, b) { var aN = parseInt(a.replace(reN, ""), 10); var bN = parseInt(b.replace(reN, ""), 10); if (aN === bN) { var aA = a.replace(reA, ""); var bA = b.replace(reA, ""); return aA === bA ? 0 : aA > bA ? 1 : -1; } else { return aN > bN ? 1 : -1; } }` – epascarello Nov 22 '19 at 13:40
  • I dusted off my alphanum comparison routine and compared it to @epascarello's version here. Mine gives better results and runs five times faster. See https://alphanum.timkay.com/cmpalphanum.js?run=edit – timkay Mar 12 '21 at 01:50
  • man, thank you soooo much for that solution! – metamorph_online Dec 19 '21 at 05:47
  • See https://github.com/timkay/alphanum.timkay.com. Runs 10x faster than other JavaScript implementation. Lower memory footprint too. – timkay Nov 17 '22 at 18:25
11

I had a similar situation, but, had a mix of alphanumeric & numeric and needed to sort all numeric first followed by alphanumeric, so:

A10
1
5
A9
2
B3
A2

needed to become:

1
2
5
A2
A9
A10
B3

I was able to use the supplied algorithm and hack a bit more onto it to accomplish this:

var reA = /[^a-zA-Z]/g;
var reN = /[^0-9]/g;
function sortAlphaNum(a,b) {
    var AInt = parseInt(a, 10);
    var BInt = parseInt(b, 10);

    if(isNaN(AInt) && isNaN(BInt)){
        var aA = a.replace(reA, "");
        var bA = b.replace(reA, "");
        if(aA === bA) {
            var aN = parseInt(a.replace(reN, ""), 10);
            var bN = parseInt(b.replace(reN, ""), 10);
            return aN === bN ? 0 : aN > bN ? 1 : -1;
        } else {
            return aA > bA ? 1 : -1;
        }
    }else if(isNaN(AInt)){//A is not an Int
        return 1;//to make alphanumeric sort first return -1 here
    }else if(isNaN(BInt)){//B is not an Int
        return -1;//to make alphanumeric sort first return 1 here
    }else{
        return AInt > BInt ? 1 : -1;
    }
}
var newlist = ["A1", 1, "A10", "A11", "A12", 5, 3, 10, 2, "A2", "A3", "A4", "B10", "B2", "F1", "F12", "F3"].sort(sortAlphaNum);
cmcculloh
  • 47,596
  • 40
  • 105
  • 130
  • 1
    `["a25b", "ab", "a37b"]` will produces `[ "a25b", "ab", "a37b" ]` instead of `[ "a25b", "a37b", "ab" ]`. – 林果皞 Dec 21 '17 at 20:07
11

You can use Intl.Collator

It has performance benefits over localeCompare Read here

Browser comparability ( All the browser supports it )

let arr = ["A1", "A10", "A11", "A12", "A2", "A3", "A4", "B10", "B2", "F1", "F12", "F3"]

let op = arr.sort(new Intl.Collator('en',{numeric:true, sensitivity:'accent'}).compare)

console.log(op)
Code Maniac
  • 37,143
  • 5
  • 39
  • 60
  • 1
    I tried benchmarking this in Node.js and it is hugely faster than the localeCompare answers. I tested both over 1000 iterations and the localeCompare took 3.159 seconds, this Intl.Collator took just 200.178ms under the same conditions, pretty impressive. – PurplProto Feb 24 '21 at 16:43
9

A simple way to do this is use the localeCompare() method of JavaScript https://www.w3schools.com/jsref/jsref_localecompare.asp

Example:

export const sortAlphaNumeric = (a, b) => {
    // convert to strings and force lowercase
    a = typeof a === 'string' ? a.toLowerCase() : a.toString();
    b = typeof b === 'string' ? b.toLowerCase() : b.toString();

    return a.localeCompare(b);
};

Expected behavior:

1000X Radonius Maximus
10X Radonius
200X Radonius
20X Radonius
20X Radonius Prime
30X Radonius
40X Radonius
Allegia 50 Clasteron
Allegia 500 Clasteron
Allegia 50B Clasteron
Allegia 51 Clasteron
Allegia 6R Clasteron
Alpha 100
Alpha 2
Alpha 200
Alpha 2A
Alpha 2A-8000
Alpha 2A-900
Callisto Morphamax
Callisto Morphamax 500
Callisto Morphamax 5000
Callisto Morphamax 600
Callisto Morphamax 6000 SE
Callisto Morphamax 6000 SE2
Callisto Morphamax 700
Callisto Morphamax 7000
Xiph Xlater 10000
Xiph Xlater 2000
Xiph Xlater 300
Xiph Xlater 40
Xiph Xlater 5
Xiph Xlater 50
Xiph Xlater 500
Xiph Xlater 5000
Xiph Xlater 58
Mitali Bhokare
  • 177
  • 1
  • 2
  • 11
3
var a1 =["A1", "A10", "A11", "A12", "A2", "A3", "A4", "B10", "B2", "F1", "F12", "F3"];

var a2 = a1.sort(function(a,b){
    var charPart = [a.substring(0,1), b.substring(0,1)],
        numPart = [a.substring(1)*1, b.substring(1)*1];

    if(charPart[0] < charPart[1]) return -1;
    else if(charPart[0] > charPart[1]) return 1;
    else{ //(charPart[0] == charPart[1]){
        if(numPart[0] < numPart[1]) return -1;
        else if(numPart[0] > numPart[1]) return 1;
        return 0;
    }
});

$('#r').html(a2.toString())

http://jsfiddle.net/8fRsD/

Josiah Ruddell
  • 29,697
  • 8
  • 65
  • 67
3

This could do it:

function parseItem (item) {
  const [, stringPart = '', numberPart = 0] = /(^[a-zA-Z]*)(\d*)$/.exec(item) || [];
  return [stringPart, numberPart];
}

function sort (array) {
  return array.sort((a, b) => {
    const [stringA, numberA] = parseItem(a);
    const [stringB, numberB] = parseItem(b);
    const comparison = stringA.localeCompare(stringB);
    return comparison === 0 ? Number(numberA) - Number(numberB) : comparison;
  });
}

console.log(sort(['A1', 'A10', 'A11', 'A12', 'A2', 'A3', 'A4', 'B10', 'B2', 'F1', 'F12', 'F3']))
console.log(sort(['a25b', 'ab', 'a37b']))
Jan
  • 8,011
  • 3
  • 38
  • 60
3

I recently worked on a project involving inventory and bin locations. The data needed to be sorted by bin location and was in an array of objects.

For anyone wanting to handle the sorting of this type of data, and your data is in an array of objects, you can do this:

const myArray = [
    { location: 'B3',   item: 'A', quantity: 25 },
    { location: 'A11',  item: 'B', quantity: 5 },
    { location: 'A6',   item: 'C', quantity: 245 },
    { location: 'A9',   item: 'D', quantity: 15 },
    { location: 'B1',   item: 'E', quantity: 65 },
    { location: 'SHOP', item: 'F', quantity: 42 },
    { location: 'A7',   item: 'G', quantity: 57 },
    { location: 'A3',   item: 'H', quantity: 324 },
    { location: 'B5',   item: 'I', quantity: 4 },
    { location: 'A5',   item: 'J', quantity: 58 },
    { location: 'B2',   item: 'K', quantity: 45 },
    { location: 'A10',  item: 'L', quantity: 29 },
    { location: 'A4',   item: 'M', quantity: 11 },
    { location: 'B4',   item: 'N', quantity: 47 },
    { location: 'A1',   item: 'O', quantity: 55 },
    { location: 'A8',   item: 'P', quantity: 842 },
    { location: 'A2',   item: 'Q', quantity: 67 }
];

const sortArray = (sourceArray) => {
    const sortByLocation = (a, b) => a.location.localeCompare(b.location, 'en', { numeric: true });
    //Notice that I specify location here ^^       and here        ^^ using dot notation
    return sourceArray.sort(sortByLocation);
};


console.log('unsorted:', myArray);

console.log('sorted by location:', sortArray(myArray));

You can easily sort by any of the other keys as well. In this case, item or quantity using dot notation as shown in the snippet.

Rob Moll
  • 3,345
  • 2
  • 9
  • 15
3

Javascript Array Sort function takes 1 optional argument that compare function. You can set this compare function as your requirements.

arr.sort([compareFunction])

compareFunction(Optional). Specifies a function that defines the sort order. If omitted, the array is sorted according to each character's Unicode code point value, according to the string conversion of each element. - MDN

Zach Jensz
  • 3,650
  • 5
  • 15
  • 30
saybanhey
  • 31
  • 4
2

Adding to the accepted answer from epascarello, since I cannot comment on it. I'm still a noob here. When one of the strinngs doesn't have a number the original answer will not work. For example A and A10 will not be sorted in that order. Hence you might wamnt to jump back to normal sort in that case.

var reA = /[^a-zA-Z]/g;
var reN = /[^0-9]/g;
function sortAlphaNum(a,b) {
    var aA = a.replace(reA, "");
    var bA = b.replace(reA, "");
    if(aA === bA) {
      var aN = parseInt(a.replace(reN, ""), 10);
      var bN = parseInt(b.replace(reN, ""), 10);
      if(isNaN(bN) || isNaN(bN)){
        return  a > b ? 1 : -1;
      }
      return aN === bN ? 0 : aN > bN ? 1 : -1;
    } else {
     return aA > bA ? 1 : -1;
    }
 }
 ["A1", "A10", "A11", "A12", "A2", "A3", "A4", "B10", "B2", "F1", "F12","F3"].sort(sortAlphaNum);`
SunnyPenguin
  • 137
  • 4
  • 15
2

Only problem with the above given solution was that the logic failed when numeric data was same & alphabets varied e.g. 28AB, 28PQR, 28HBC. Here is the modified code.

var reA = /[^a-zA-Z]/g;
    var reN = /[^0-9]/g;
    var AInt = parseInt(a, 10);
    var BInt = parseInt(b, 10);
    if(isNaN(AInt) && isNaN(BInt)){
        var aA = a.replace(reA, "");
        var bA = b.replace(reA, "");
        if(aA === bA) {
            var aN = parseInt(a.replace(reN, ""), 10);
            var bN = parseInt(b.replace(reN, ""), 10);
            alert("in if "+aN+" : "+bN);
            return aN === bN ? 0 : aN > bN ? 1 : -1;
        } else {
            return aA > bA ? 1 : -1;
        }
    }else if(isNaN(AInt)){//A is not an Int
        return 1;//to make alphanumeric sort first return 1 here
    }else if(isNaN(BInt)){//B is not an Int
        return -1;//to make alphanumeric sort first return -1 here
    }else if(AInt == BInt) {
        var aA = a.replace(reA, "");
        var bA = b.replace(reA, "");
        return aA > bA ? 1 : -1;
    }
    else {
        return AInt > BInt ? 1 : -1;
    }
1

Here is an ES6 Typescript upgrade to this answer.

export function SortAlphaNum(a: string, b: string) {
    const reA = /[^a-zA-Z]/g;
    const reN = /[^0-9]/g;
    const aA = a.replace(reA, "");
    const bA = b.replace(reA, "");
    if (aA === bA) {
      const aN = parseInt(a.replace(reN, ""), 10);
      const bN = parseInt(b.replace(reN, ""), 10);
      return aN === bN ? 0 : aN > bN ? 1 : -1;
    } else {
      return aA > bA ? 1 : -1;
    }
}
Niranth Reddy
  • 493
  • 1
  • 11
  • 27
Devin Prejean
  • 626
  • 6
  • 12
1

Well, just in case someone's looking for a cleaner approach using localeCompare

arr.sort((a, b) => a.localeCompare(b, undefined,{ numeric: true }))
Gustavo Barros
  • 116
  • 1
  • 7
0

I have solved the above sorting problem with below script

arrVals.sort(function(a, b){
    //return b.text - a.text;
    var AInt = parseInt(a.text, 10);
    var BInt = parseInt(b.text, 10);

    if ($.isNumeric(a.text) == false && $.isNumeric(b.text) == false) {
        var aA = a.text
        var bA = b.text;
        return aA > bA ? 1 : -1;
    } else if ($.isNumeric(a.text) == false) {  // A is not an Int
        return 1;    // to make alphanumeric sort first return -1 here
    } else if ($.isNumeric(b.text) == false) {  // B is not an Int
        return -1;   // to make alphanumeric sort first return 1 here
    } else {
        return AInt < BInt ? 1 : -1;
    }
});

This works fine for a well mixed array.:)

Thank you.

Varit J Patel
  • 3,497
  • 1
  • 13
  • 21
Seema
  • 1
0
alphaNumericCompare(a, b) {

    let ax = [], bx = [];

    a.replace(/(\d+)|(\D+)/g, function (_, $1, $2) { ax.push([$1 || Infinity, $2 || '']) });
    b.replace(/(\d+)|(\D+)/g, function (_, $1, $2) { bx.push([$1 || Infinity, $2 || '']) });

    while (ax.length && bx.length) {
       let an = ax.shift();
       let bn = bx.shift();
       let nn = (an[0] - bn[0]) || an[1].localeCompare(bn[1]);
       if (nn) {
         return nn;
       }
     }
     return ax.length - bx.length;
}
Hexfire
  • 5,945
  • 8
  • 32
  • 42
0

This has worked for me and it's a bit more compact.

const reg = /[0-9]+/g;

array.sort((a, b) => {
     let v0 = a.replace(reg, v => v.padStart(10, '0'));
     let v1 = b.replace(reg, v => v.padStart(10, '0'));
     return v0.localeCompare(v1);
});
WaitsAtWork
  • 295
  • 1
  • 13
0

Here's a version (based on the answer of @SunnyPenguin & @Code Maniac) that is in TypeScript as a library function. Variable names updated & comments added for clarity.

// Sorts strings with numbers by keeping the numbers in ascending order
export const sortAlphaNum: Function = (a: string, b: string, locale: string): number => {
  const letters: RegExp = /[^a-zA-Z]/g;
  const lettersOfA: string = a.replace(letters, '');
  const lettersOfB: string = b.replace(letters, '');

  if (lettersOfA === lettersOfB) {
    const numbers: RegExp = /[^0-9]/g;
    const numbersOfA: number = parseInt(a.replace(numbers, ''), 10);
    const numbersOfB: number = parseInt(b.replace(numbers, ''), 10);

    if (isNaN(numbersOfA) || isNaN(numbersOfB)) {
      // One is not a number - comparing letters only
      return new Intl.Collator(locale, { sensitivity: 'accent' }).compare(a, b);
    }
    // Both have numbers - compare the numerical parts
    return numbersOfA === numbersOfB ? 0 : numbersOfA > numbersOfB ? 1 : -1;
  } else {
    // Letter parts are different - comparing letters only
    return new Intl.Collator(locale, { sensitivity: 'accent' }).compare(lettersOfA, lettersOfB);
  }
};
Russ
  • 623
  • 8
  • 14
-3
function sortAlphaNum(a, b) {
    var smlla = a.toLowerCase();
    var smllb = b.toLowerCase();
    var result = smlla > smllb ? 1 : -1;
    return result;
}
Clay
  • 1