Sorting array of mixed clothing sizes in javascript

Viewed 1800

Say I have an array that looks like this:

$array = ['xl', 's', '1', '10', '3', 'xs', 'm', '3T', 'xxl', 'xxs', 'one size'];

I want to sort the array to look like this:

$sortedArray = ['1', '3', '3T', '10', 'one size', 'xxs', 'xs', 's', 'm', 'xl', 'xxl'];

How could I possibly sort this array in javascript?

I can spot a pattern so that helps me get on the right track, but I can't figure out the sort function. A pattern being all sizes that start with a number are first, ordered numerically (but not sure how '3T' handles that). And then we show 'one size', and then we sort the rest (XXS, XS, S, M, L, XL, XXL) based on a predefined order.

4 Answers

Using sort with userdefined sort algorithm: I parse both both to compare to an Int. Than I look if only one of them is a number than this is before. If both are numbers than I look if both are equal. If so than I take the rest from the string (which is not part of the Int) and compare them alphabetically. Otherwise I compare both Integers.
If none of abouve cases is true than I have my ORDER-array with the confection sizes and I sort for the indexOf this. If there are missing any, you can easily add them.

Extended: Because the sizes are sometimes xxl or XXL I convert them for sorting to lowercases so they are sorted as desired.

Extended 2: Because 2XL would be sorted to the beginning to the numbers I make another trick: After parsing to integer I look if the string is one out of the ORDER-array. If so I set the parsed integer to NaN like there stnds a string. By this the comparison for the keywords takes place for this entry.

Extended 3: Sorting of 0 was false, I added it to the begin (like OP's proposal).

array = ['XL', 's', '1', '10', '2xl', '3', '0', 'xs', 'm', '3T', 'xxl', 'xxs', 'one size'];
const ORDER = ['one size', 'xxs', 'xs', 's', 'm', 'xl', '2xl', 'xxl'];

array.sort((a,b) => {
    a = a.toLowerCase();
    b = b.toLowerCase();
    
    let nra = parseInt(a);
    let nrb = parseInt(b);
    
    if ((ORDER.indexOf(a)!=-1)) nra = NaN;
    if ((ORDER.indexOf(b)!=-1)) nrb = NaN;
  
    if (nrb===0) return 1;
    if (nra&&!nrb || nra===0) return -1;
    if (!nra&&nrb) return 1;
    if (nra && nrb) {
        if (nra==nrb) {
            return (a.substr((''+nra).length)).localeCompare((a.substr((''+nra).length)));
        } else {
            return nra-nrb;
        }
    } else {
        return ORDER.indexOf(a) - ORDER.indexOf(b);
    }
});

console.log(array);

You can define the weights of each size, and then sort according to it:

let array = ['xl', 's', '1', '10', '3', 'xs', 'm', '3T', 'xxl', 'xxs', 'one size'];
let weights = {
  '1':1, 
  '3':2, 
  '3T':3, 
  '10':4, 
  'one size':5, 
  'xxs':6, 
  'xs':7, 
  's':8, 
  'm':9, 
  'xl':10, 
  'xxl':11
};
let sortedArray = array.sort((a,b)=>weights[a]-weights[b]);
console.log(sortedArray)

Another way to think about this, would be comparing the values normally if both are numeric, and rely on weights comparison otherwise:

let array = ['xl', 's', '1', '10', '3', 'xs', 'm', '3T', 'xxl', 'xxs', 'one size'];
let weights = {
  '1':1, 
  '3':2, 
  '3T':3, 
  '10':4, 
  'one size':5, 
  'xxs':6, 
  'xs':7, 
  's':8, 
  'm':9, 
  'xl':10, 
  'xxl':11
};
let sortedArray = array.sort((a,b)=>{
     if(typeof(a)=="number" && typeof(b)=="number")
          return a-b;
     else
          return weights[a]-weights[b]
});
console.log(sortedArray)

I would avoid predefined weights and orders, and simply set s = -1; m = 0; l = 1 and let preceeding xes be multipliers. Additionally, sort numbers before strings, as there's no point in mixing different grading systems.

function sortArrayOfGradings(array) {
    function parseGradingOrder(grading) {
        let order;
        if (grading.includes('s'))
            order = -1;
        else if (grading.includes('m'))
            order = 0;
        else if (grading.includes('l'))
            order = 1;
        const n = Number(grading.match(/\d+(?!X)/))
        const numXes = grading.match(/x*/)[0].length
        const mul = n ? n : numXes + 1
        return order * mul;
    }
    return array.sort((a, b) => {
        if (!isNaN(a) && !isNaN(b))
              return a-b;
        if (!isNaN(a) && isNaN(b))
            return -1
        if (isNaN(a) && !isNaN(b))
            return 1
        if (isNaN(a) && isNaN(b)) {
            let aOrder = parseGradingOrder(a.toLowerCase());
            let bOrder = parseGradingOrder(b.toLowerCase());
            return aOrder-bOrder;
        }
    });
}

Then you could sort any thing like 'XXXl' and '2xl' together (not case sensitive):

sortArrayOfGradings(['xxxxl', '2xl', 'l'])
// returns ['l', '2xl', 'xxxxl']

And:

sortArrayOfGradings(['xxxxl', '5xl', 'l'])
// returns ['l', 'xxxxl', '5xl']

But you could also throw numbers in there:

sortArrayOfGradings([2, 2, 1, 4, 'XL', '2xl', 'xs'])
// returns [1, 2, 2, 4, 'xs', 'XL', '2xl']

The short answer is: you don't.

I am assuming that what you really want to do is to be able to sort other things based on their size, where the order is determined by some (as far as Javascript is concerned, arbitrary) ordering specified by a list. So you would do just that: write a sort routine that would compare items by their size's relative position in that list.

Or just look at @MajedBadawi's answer.

Related