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;
}
Oussama
  • 63
  • 1
  • 9
  • what about lower and upper case letters? – Nina Scholz Sep 27 '20 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. – Oussama Sep 27 '20 at 18:16
  • Are you looking for https://stackoverflow.com/questions/15478954/sort-array-elements-string-with-numbers-natural-sort? Or did you find that and have a problem with your specific implementation not leading to the desired order? – Bergi Sep 27 '20 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. – Oussama Sep 27 '20 at 19:01

2 Answers2

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);
trincot
  • 317,000
  • 35
  • 244
  • 286
  • Is this possible with the compare function so it can be used for objects? – Oussama Sep 28 '20 at 09:11
  • 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. – trincot Sep 28 '20 at 09:47
  • See also addition to answer. – trincot Sep 28 '20 at 09:53
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; }
Nina Scholz
  • 376,160
  • 25
  • 347
  • 392