0

I'm trying to sort array using a function. But when the number is going to two digit sorting is not correct. Otherwise it's showing correct result. Please check the code.

this is the function:

var arr = [{name: "a1", value: 1},{name: "a3", value: 1},{name: "a4", value: 1},{name: "a2", value: 1}];

var arr2 = [{name: "a1", value: 1},{name: "a3", value: 1},{name: "a14", value: 1},{name: "a12", value: 1}];

var sort = function (prop, arr) {  
    prop = prop.split('.');
    var len = prop.length;
    
    arr.sort(function (a, b) {
        var i = 0;
        while( i < len ) {
            a = a[prop[i]];
            b = b[prop[i]];
            i++;
        }
        if (a < b) {
            return -1;
        } else if (a > b) {
            return 1;
        } else {
            return 0;
        }
    });
    return arr;
};
arr = sort('name', arr);
arr2 = sort('name', arr2);
console.log(arr); // it's correct
console.log(arr2); // it's not correct

In this case I'm trying to sort the array based on value of name.

1
  • 2
    Use .localeCompare to compare the strings. Although the sort criteria is ambiguous..a3 could mean both a03 and a30 (for comparison purposes) Commented Apr 26, 2020 at 9:22

3 Answers 3

3

You can use numeric collation with String#localeCompare. It will avoid lexicographic sorting and give you proper numeric order:

var a = "10";
var b = "2";

console.log( //false - string "10" does not come after "2" because 1 < 2
  "a > b:", 
   a > b
);
console.log( //-1 - `a` comes before `b`
  "a.localeCompare(b):", 
   a.localeCompare(b)
);
console.log( // 1 - `a` comes after `b`
  "a.localeCompare(b, undefined, {numeric: true}):",
   a.localeCompare(b, undefined, {numeric: true})
);

Note that this works for strings. If you also want to sort numbers, you have two easy options:

  • Cast the numbers to strings then use the string sorting with numeric collation. It will work the same and it keeps your code simple.
  • Use an if to check for what you're comparing and apply the .localeCompare logic for strings and a different logic for numbers.

Both would work but I personally prefer the first one, since it doesn't involve maintaining different cases. Thus using it in your solution gives the correct results:

var arr = [{name: "a1", value: 1},{name: "a3", value: 1},{name: "a4", value: 1},{name: "a2", value: 1}];

var arr2 = [{name: "a1", value: 1},{name: "a3", value: 1},{name: "a14", value: 1},{name: "a12", value: 1}];

var arr3 = [{name: "a", value: 3},{name: "a", value: 1},{name: "a", value: 14},{name: "a", value: 12}];

var sort = function (prop, arr) {  
    prop = prop.split('.');
    var len = prop.length;
    
    arr.sort(function (a, b) {var i = 0;
        while( i < len ) {
            a = a[prop[i]];
            b = b[prop[i]];
            i++;
        }
        
        //cast to string
        a = String(a);
        b = String(b);
        
        return a.localeCompare(b, undefined, {numeric: true})
    });
    return arr;
};
arr = sort('name', arr);
arr2 = sort('name', arr2);
console.log(arr); // it's correct
console.log(arr2); // it's correct

arr3 = sort('value', arr3);
console.log(arr3); // it's correct

4
  • for the numeric key use +1, Commented Apr 26, 2020 at 9:32
  • No longer works if sort function will be used for 'value' property Commented Apr 26, 2020 at 9:35
  • @Aleksey you're right. I missed that. One easy way around it is to cast both to strings and still rely on collation. The other is to do if/else and check what you're actually comparing. I'll add this to the answer. Commented Apr 26, 2020 at 9:37
  • Or just simply a = "" + a and b = "" + b and then it will work for all the cases Commented Apr 26, 2020 at 9:38
1

You need to split the alphabets and digits and then compare them

var arr = [{name: "a1", value: 1},{name: "a3", value: 1},{name: "a4", value: 1},{name: "a2", value: 1}];

var arr2 = [{name: "a1", value: 1},{name: "a3", value: 1},{name: "a14", value: 1},{name: "a12", value: 1}];

var arr3 = [{name: 1, value: 1},{name: 11, value: 1},{name: 14, value: 1},{name: 12, value: 1}];
var sort = function (prop, arr) {  
    prop = prop.split('.');
    var len = prop.length;
    
    arr.sort(function (a, b) {
        var i = 0;
        while( i < len ) {
            a = a[prop[i]];
            b = b[prop[i]];
            i++;
        }
        let [key1,digit1] = (""+a).split(/(?<=[a-z])(?=\d)/)
        let [key2,digit2] = (""+b).split(/(?<=[a-z])(?=\d)/)
        return (key1 - key2) || (+digit1 - +digit2)
    });
    return arr;
};
arr = sort('name', arr);
arr2 = sort('name', arr2);
arr3 = sort('name', arr3)
console.log(arr); // it's correct
console.log(arr2); // it's not correct
console.log(arr3);

5
  • But this no longer works if he will use this function to sort by value Commented Apr 26, 2020 at 9:25
  • First array is the same with this method Commented Apr 26, 2020 at 9:28
  • @Ali i've fixed the answer you can see the updated one Commented Apr 26, 2020 at 9:33
  • SyntaxError: invalid regexp group in Firefox. The lookbehinds work in Chrome, for now. Commented Apr 26, 2020 at 9:34
  • @VLAZ yes there's no support for lookbehind in firefox yet Commented Apr 26, 2020 at 9:35
-1

Name property is of type string that is why sort is performed in lexicographical order(ie alphabetical order). You would need to parse name field to sort by number inside name.

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.