0

I have been searching for this couldn't find it and tried implementing myself with regex but order is never right.

I need to apply to object with compare function, but let's say we have this array

test = [
    "Oussama",
    "Tarik R",
    "Adam",
    "Tarik2",
    "12",
    "13 x",
    " ",
    "@",
    "🤗",
    "Zayn",
];

I want it to be sorted like ASCII sort but letters to be first like

test = [
    "Adam",
    "Tarik R",
    "Tarik2", //the part after this doesn't matter if symbols first or numbers
    "Zayn",
    "12",
    "13 x",
    " ",
    "@",
    "🤗",
];

What I have tried is testing with natural sort and regex but failed.

At first created 3 regex to compare with

//Regular name, could include a number or a char but doesn't start with it, ex Oussama, Oussama R, Oussama 2 rabat
const regName = /^[a-zA-Z].*[\s\.]*$/;

//Start with a number, not to be sorted first, ex 1 Oussama, 2Reda, 2# tarik
const regNumber =/^[0-9].*[\s\.]*/;

//Start with a symbol,ex @Oussama,  2@ Mohamed zenéée
const regSymbol = /^[ -!@#$%^&*()_+|~=`{}\[\]:";'<>?,.\/].*[\s\.]*/;

Then I found this solution, tested it, updated it and of course it didn't work for my case

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


function naturalCompare(a, b) {
    var ax = [], bx = [];

    a.replace(reN, function(_, $1, $2) { ax.push([$1 || Infinity, $2 || ""]) });
    b.replace(reL, function(_, $1, $2) { bx.push([$1 || Infinity, $2 || ""]) });
    
    while(ax.length && bx.length) {
        var an = ax.shift();
        var bn = bx.shift();
        var nn = (an[0] - bn[0]) || an[1].localeCompare(bn[1]);
        if(nn) return nn;
    }

    return ax.length - bx.length;
}
4
  • what about lower and upper case letters? Commented Sep 27, 2020 at 17:53
  • They arent very important, its case insensitive. Could be Adam followed by bilal by Joseph9 by Zayn x, but then numbers and symbols later, I just have a contact list and I want to order so the user can see names first not symbols or numbers. But has to be sorted. I literally Googled everywhere so I thought to make this for other devs who will search it in the future. Hope someone can help. Commented Sep 27, 2020 at 18:16
  • Are you looking for stackoverflow.com/questions/15478954/…? Or did you find that and have a problem with your specific implementation not leading to the desired order? Commented Sep 27, 2020 at 18:24
  • It didn’t let to the order I want, I’m just trying to sort a mixed array with names, characters and numbers like ASCII sort just start with the letters first. Commented Sep 27, 2020 at 19:01

2 Answers 2

3

In each string, you could prefix each letter with a category number ("1" for letters, "2" for digits, "3" for other characters), and append the original string to that result, so that you get strings that are three times as long as in the original input. Then sort that. Finally extract the original string from each sorted entry, which is the last one-third part of each string:

let test = ["Oussama", "Tarik R", "Adam", "bilal", "Joseph9", "Tarik2", "12", "13 x", " ", "@", "🤗", "Zayn"];

let result = test.map(a =>
    a.toLowerCase().replace(/([a-z])|(\d)|./sg, (m, l, d) => (l ? 1 : d ? 2 : 3) + m) + a
).sort().map(a => a.slice(-a.length / 3));
console.log(result);

If your array has objects, which have a property that is the string to sort by, then it will be easier to map the objects to a pair having the "category" string, and the original object:

let test = [{name: "Oussama"}, {name: "Tarik R"}, {name:  "Adam"}, {name:  "bilal"}, {name:  "Joseph9"}, {name:  "Tarik2"}, {name:  "12"}, {name:  "13 x"}, {name:  " "}, {name:  "@"}, {name:  "🤗"}, {name:  "Zayn"}];

let result = test.map(a =>
    [a.name.toLowerCase().replace(/([a-z])|(\d)|./sg, (m, l, d) => (l ? 1 : d ? 2 : 3) + m), a]
).sort(([a], [b]) => a.localeCompare(b)).map(a => a[1]);
console.log(result);

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

3 Comments

Is this possible with the compare function so it can be used for objects?
Do you mean you want an argument given to sort? Then just do sort((a,b) => a.localeCompare(b)). But that is the default when you don't pass a comparator. If you have a new question, then I suggest you ask a new question. Don't change the question here to add new requirements. If you ask a new question, then also adapt it with an example that has objects, so people understand what you want to solve.
See also addition to answer.
1

You could separate the strings into characters and add spaces to each character to get three different groups for sorting.

The first check adds two space in front of the caracter for letters, the second check for digits and adds a space in fron and another at the end and all other characters except of space gets the spaces at the end.

Then a sorting takes place and the the most early coming caracters except of space are sorted to the end.

const
    array = ["Oussama", "Tarik R", "Adam", "bilal", "Tarik2", "BILAL", "12", "13 x", " ", "@", "🤗", "Zayn"],
    result = array
        .map((string, index) => {
            let value = '';

            for (const c of string) {
                if (/[A-Z]/i.test(c)) {
                    value += '  ' + c;
                    continue;
                }
                if (/\d/.test(c)) {
                    value += ' ' + c + ' ';
                    continue;
                }
                value += c === ' ' ? '___' : c + '  ';
            }
            return { index, value };
        })
       .sort((a, b) => a.value.localeCompare(b.value))
       .map(({ index }) => array[index]);

console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Sorting with callback.

const
    customFn = value => {
        let result = '';
        for (const c of value.toString()) {
            if (/[A-Z]/i.test(c)) {
                result += '  ' + c;
                continue;
            }
            if (/\d/.test(c)) {
                result += ' ' + c + ' ';
                continue;
            }
            result += c === ' ' ? '___' : c + '  ';
        }
        return result;
    },
    sortFn = wrapper => (a, b) => wrapper(a).localeCompare(wrapper(b)),
    array = ["Oussama", "Tarik R", "Adam", "bilal", "Tarik2", "BILAL", "12", "13 x", " ", "@", "🤗", "Zayn"];

console.log(array.sort(sortFn(customFn)));
.as-console-wrapper { max-height: 100% !important; top: 0; }

1 Comment

Thank you a lot, Is this possible with the compare function so it can be used for objects?

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.