97

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?

2
  • Are the numerical values always at the end of the string? Commented Dec 2, 2010 at 21:47
  • 2
    Possible duplicate of How to sort strings in JavaScript Commented Aug 28, 2018 at 11:35

18 Answers 18

133
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.

Sign up to request clarification or add additional context in comments.

2 Comments

This should be the accepted answer IMO. Small nit: The first line contains a trailing backtick after { numeric: true })
@w3dot0: A agree with you, but if you understand how time works, an answer given on 5/26/2017 can not magically become an accepted answer, replacing an answer that I accepted on 12/2/2010. It does not looks like localeComapare() existed in 2010 either
112

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)
)

20 Comments

So catching up a little late...but you don't need the else block since the first if will return if aA === bA
@Noitidart preference. There should be no difference between the two.
This is a good answer but it very badly needs comments. It took me awhile to read this and for it to make sense.
@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.
no offense of course, because it did really help me. +1.
|
14

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)

1 Comment

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.
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);

1 Comment

["a25b", "ab", "a37b"] will produces [ "a25b", "ab", "a37b" ] instead of [ "a25b", "a37b", "ab" ].
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

2 Comments

This should be the answer. Nice!
The items are not ordered correctly! 1000X should come after 10X, for example.
4

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']))

Comments

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/

Comments

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.

Comments

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

Comments

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);`

Comments

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;
    }

2 Comments

best answer for considering all kinds of mixed values - thanks a lot! :)
Take the alert() out of the example and it works great :-)
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;
    }
}

Comments

1

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

arr.sort((a, b) => a.localeCompare(b, undefined,{ numeric: true }))

Comments

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.

Comments

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;
}

Comments

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);
});

Comments

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);
  }
};

Comments

-3
function sortAlphaNum(a, b) {
    var smlla = a.toLowerCase();
    var smllb = b.toLowerCase();
    var result = smlla > smllb ? 1 : -1;
    return result;
}

1 Comment

This is wrong. Try comparing A10 to A2. This will sort A10 before A2, but A2 should be sorted before A10.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.