2

I am trying to sort an array of objects by the name field but the ordering is wrong.

The order I am getting is [1, 10, 11, 2, 20, 21, 3]

Instead of [1, 2, 3, .... 10, 11, 20, 21]

It is sorting but putting 10 ahead of 2

Here is the code I am currently using.

const arr = [
    
    {
        "name": "Action 10",
        "color": "transparent",
        "type": "components"
    },
    {
        "name": "Action 11",
        "color": "transparent",
        "type": "components"
    },
    {
        "name": "Action 2",
        "color": "transparent",
        "type": "components"
    },
    {
        "name": "Action 20",
        "color": "transparent",
        "type": "components"
    },
    {
        "name": "Action 21",
        "color": "transparent",
        "type": "components"
    },
    {
        "name": "Action 3",
        "color": "transparent",
        "type": "components"
    },
    {
        "name": "Action 4",
        "color": "transparent",
        "type": "components"
    },
    {
        "name": "Action 5",
        "color": "transparent",
        "type": "components"
    },
    {
        "name": "Action 6",
        "color": "transparent",
        "type": "components"
    },
    {
        "name": "Action 1",
        "color": "transparent",
        "type": "components"
    }
]

function sorter(a, b) {
    if (a.name < b.name) return -1;
    if (a.name > b.name) return 1;
    return 0;
}

console.log(arr.sort(sorter));

4
  • This is because it's sorting by string (not numerically) so "10" comes before "2". If you want a numeric sort, you'll need to split the strings to get the # and sort on that. Could the "name" have any other pattern than "Action #"? Commented Mar 31, 2022 at 22:39
  • @AndyJamesN ... From all the provided answers / approaches / solutions are there any questions left? Commented Apr 4, 2022 at 11:38
  • @PeterSeliger Nope it is clear now and all solutions work. I am just curious.. is there a reason your solution is better than the reg ex or other solution? I am not entirely sure which would be considered the best answer. Commented Apr 5, 2022 at 16:45
  • @AndyJamesN ... as mostly the answer is ... "It depends." . For the OP's special use case of an over and over repeating pattern of just a single same word part like 'Action' as in "Action 1" and a sole digit sequence part like '21' as in "Action 21" the regex based solutions which are just after matching digits via /\d+/ and parsing and sorting it are equal. But any of these approaches fail already for a lowercase "action 10" versus the original "Action 10" and of cause for any word other than 'Action' within an item's name property. Commented Apr 5, 2022 at 16:57

4 Answers 4

3

One could give the numeric sorting option of String.prototype.localeCompare a try ...

const arr = [{
  name: "Action 10", color: "transparent", type: "components"
}, {
  name: "Action 11", color: "transparent", type: "components"
}, {
  name: "Action 2", color: "transparent", type: "components"
}, {
  name: "Action 20", color: "transparent", type: "components"
}, {
  name: "Action 21", color: "transparent", type: "components"
}, {
  name: "Action 3", color: "transparent", type: "components"
}, {
  name: "Action 4", color: "transparent", type: "components"
}, {
  name: "Action 5", color: "transparent", type: "components"
}, {
  name: "Action 6", color: "transparent", type: "components"
}, {
  name: "Action 1", color: "transparent", type: "components"
}];

function sorter(a, b) {
  return a.name.localeCompare(b.name, undefined, { numeric: true });
}
console.log(arr.sort(sorter));
.as-console-wrapper { min-height: 100%!important; top: 0; }

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

Comments

1

The ordering is correct. The string "Action 10" collates greater than Action 1 and less than Action 2, because string comparison is done on a character by character basis.

The algorithm for string comparision is found here: https://262.ecma-international.org/5.1/#sec-11.8.5

[If] both px and py are Strings

  • If py is a prefix of px, return false. (A String value p is a prefix of String value q if q can be the result of concatenating p and some other String r. Note that any String is a prefix of itself, because r may be the empty String.)

  • If px is a prefix of py, return true.

Let k be the smallest nonnegative integer such that the character at position k within px is different from the character at position k within py. (There must be such a k, for neither String is a prefix of the other.)

  • Let m be the integer that is the code unit value for the character at position k within px.

  • Let n be the integer that is the code unit value for the character at position k within py.

  • If m < n, return true. Otherwise, return false.

Or, as a javascript function, the expression str1 < str2 is evaluated as if this function were invoked (not that anybody sane would implement the algorithm as described in the spec):

function lt( px, py ) {
    const prefixOf = (x,y) => x.slice(0,y.length) === y;

    if ( prefixOf(px,py) ) return false;
    if ( prefixOf(py,px) ) return true;

    let k = 0 ;
    while ( px[k] === py[k] ) {
        ++k;
    }
 
    m = px.charCodeAt(k);
    n = py.charCodeAt(k);

    return m < n ? true : false;
  }

If you want to order things according to the semantic meaning of name, you'll need to partition the string into a list of its non-numeric and numeric segments, convert the numeric bits into numbers, and then compare the segments in order from left to right.

Comments

0

You can use regular expressions:

const sorter = (a, b) => +a.name.match(/\d+/) - +b.name.match(/\d+/);

DEMO

const arr = [{"name": "Action 10","color": "transparent","type": "components"}, {"name": "Action 11","color": "transparent","type": "components"}, {"name": "Action 2","color": "transparent","type": "components"}, {"name": "Action 20","color": "transparent","type": "components"}, {"name": "Action 21","color": "transparent","type": "components"}, {"name": "Action 3","color": "transparent","type": "components"}, {"name": "Action 4","color": "transparent","type": "components"}, {"name": "Action 5","color": "transparent","type": "components"}, {"name": "Action 6","color": "transparent","type": "components"}, {"name": "Action 1","color": "transparent","type": "components"}]

function sorter(a, b) {
    return +a.name.match(/\d+/)[0] - +b.name.match(/\d+/)[0];
}

console.log(arr.sort(sorter));

Comments

0

const arr = [{"name": "Action 10","color": "transparent","type": "components"}, {"name": "Action 11","color": "transparent","type": "components"}, {"name": "12Action 2","color": "transparent","type": "components"}, {"name": "Action","color": "transparent","type": "components"}, {"name": "Action 21","color": "transparent","type": "components"}, {"name": "Action 3","color": "transparent","type": "components"}, {"name": "Action 4","color": "transparent","type": "components"}, {"name": "Action 5","color": "transparent","type": "components"}, {"name": "Action 6","color": "transparent","type": "components"}, {"name": "Action 1","color": "transparent","type": "components"}]

function sorter(a, b) {
    return +a.name.match(/\d+/)[0] - +b.name.match(/\d+/)[0];
}

console.log(arr.sort(sorter));

1 Comment

Thank you for contributing to the Stack Overflow community. This may be a correct answer, but it’d be really useful to provide additional explanation of your code so developers can understand your reasoning. This is especially useful for new developers who aren’t as familiar with the syntax or struggling to understand the concepts. Would you kindly edit your answer to include additional details for the benefit of the community?

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.