DEV Community

Masui Masanori
Masui Masanori

Posted on

[TypeScript] Sort formatted texts

Intro

I will try sorting a specific formatted string values.

Before

'SAMPLE1-1-4-A',
'SAMPLE1-1-4-3',
'SAMPLE1-1-2-B',
'SAMPLE1-2-1',
'SAMPLE1-2-12-2',
'SAMPLE1-2-2-F',
'SAMPLE1-2-5-A',
'SAMPLE1-3-5',
'SAMPLE2-3-10',
'SAMPLE2-4-2-10',
'SAMPLE2-4-2-1',
'SAMPLE2-1-1-14',
'SAMPLE2-14-2-Z',
'SAMPLE2-1-10-C',
'SAMPLE2-1-4-1',
'SAMPLE2-1-4',
'SAMPLE2-2-4-A',
'SAMPLE2-23-2',
'SAMPLE2-4-2-2',
'SAMPLE2-10-2'
Enter fullscreen mode Exit fullscreen mode

After

"SAMPLE1-1-2-B"
"SAMPLE1-1-4-3"
"SAMPLE1-1-4-A"
"SAMPLE1-2-1"
"SAMPLE1-2-2-F"
"SAMPLE1-2-5-A"
"SAMPLE1-2-12-2"
"SAMPLE2-1-4"
"SAMPLE2-1-4-1"
"SAMPLE2-1-10-C"
"SAMPLE2-2-4-A"
"SAMPLE2-3-10"
"SAMPLE2-4-2-1"
"SAMPLE2-4-2-2"
"SAMPLE2-4-2-10"
"SAMPLE2-4-B"
"SAMPLE2-10-2"
"SAMPLE2-14-2-Z"
"SAMPLE2-D-1-14"
"SAMPLE10-3-5"
Enter fullscreen mode Exit fullscreen mode

localeCompare (Failed)

I sorted them with "localeCompare".

sortSample.ts

export class SortSample {
    private originalTexts: readonly string[] = [
        'SAMPLE1-1-4-A',
        'SAMPLE1-1-4-3',
        'SAMPLE1-1-2-B',
        'SAMPLE1-2-1',
        'SAMPLE1-2-12-2',
        'SAMPLE1-2-2-F',
        'SAMPLE1-2-5-A',
        'SAMPLE10-3-5',
        'SAMPLE2-3-10',
        'SAMPLE2-4-2-10',
        'SAMPLE2-4-2-1',
        'SAMPLE2-D-1-14',
        'SAMPLE2-14-2-Z',
        'SAMPLE2-1-10-C',
        'SAMPLE2-1-4-1',
        'SAMPLE2-1-4',
        'SAMPLE2-2-4-A',
        'SAMPLE2-4-B',
        'SAMPLE2-4-2-2',
        'SAMPLE2-10-2'
    ];
    public sort(): readonly string[] {
        const copiedTexts = [...this.originalTexts];
        return copiedTexts.sort((a, b) => a.localeCompare(b));
    }
}
Enter fullscreen mode Exit fullscreen mode

Result

0: "SAMPLE1-1-2-B"
1: "SAMPLE1-1-4-3"
2: "SAMPLE1-1-4-A"
3: "SAMPLE1-2-1"
4: "SAMPLE1-2-12-2"
5: "SAMPLE1-2-2-F"
6: "SAMPLE1-2-5-A"
7: "SAMPLE1-3-5"
8: "SAMPLE2-1-1-14"
9: "SAMPLE2-1-10-C"
10: "SAMPLE2-1-4"
11: "SAMPLE2-1-4-1"
12: "SAMPLE2-10-2"
13: "SAMPLE2-14-2-Z"
14: "SAMPLE2-2-4-A"
15: "SAMPLE2-23-2"
16: "SAMPLE2-3-10"
17: "SAMPLE2-4-2-1"
18: "SAMPLE2-4-2-10"
19: "SAMPLE2-4-2-2"
Enter fullscreen mode Exit fullscreen mode

They were orderd like "SAMPLE2-4-2-1" -> "SAMPLE2-4-2-10" -> "SAMPLE2-4-2-2".
Numbers such as "4", "2", and "1" should be treated as "number" type instead of "string" type.

Split and parse to numbers

I Split and parse them to numbers.

sortSample.ts

export class SortSample {
    private originalTexts: readonly string[] = [
...
    ];
    public sort(): readonly string[] {
        console.log(this.compareWithSplit);

        const copiedTexts = [...this.originalTexts];
        //return copiedTexts.sort((a, b) => a.localeCompare(b));
        return copiedTexts.sort((a, b) => this.compareWithSplit(a, b))
    }    
    private compareWithSplit(a: string|null|undefined, b: string|null|undefined): number {
        if(a == null && b == null) {
            return 0;
        }
        if(a == null) {
            return 1;
        }
        if(b == null) {
            return -1;
        }
        if(a.startsWith('SAMPLE') === false) {
            return a.localeCompare(b);
        }
        const splittedA = a.replace('SAMPLE', '').split('-');
        const splittedB = b.replace('SAMPLE', '').split('-');
        let index = 0;
        for(const sa of splittedA) {
            if(index >= splittedB.length) {
                return 1;
            }
            // to ignore compile error because the type is treated as string|undefined
            const sb = splittedB[index] ?? '';
            const na = parseInt(sa);
            const nb = parseInt(sb);
            if(isNaN(na)) {
                // sa: [A-Z]
                if(isNaN(nb)) {
                    // sb: [A-Z]
                    return sa.localeCompare(sb);
                }
                return 1;
            }
            if(isNaN(nb)) {
                // sa: [0-9] sb: [A-Z]
                return -1;
            }
            if((na - nb) !== 0) {
                return (na - nb);
            }
            // if na === nb, move next values.
            index += 1;
        }
        return (splittedA.length - splittedB.length);
    }
}
Enter fullscreen mode Exit fullscreen mode

Result

0: "SAMPLE1-1-2-B"
1: "SAMPLE1-1-4-3"
2: "SAMPLE1-1-4-A"
3: "SAMPLE1-2-1"
4: "SAMPLE1-2-2-F"
5: "SAMPLE1-2-5-A"
6: "SAMPLE1-2-12-2"
7: "SAMPLE2-1-4"
8: "SAMPLE2-1-4-1"
9: "SAMPLE2-1-10-C"
10: "SAMPLE2-2-4-A"
11: "SAMPLE2-3-10"
12: "SAMPLE2-4-2-1"
13: "SAMPLE2-4-2-2"
14: "SAMPLE2-4-2-10"
15: "SAMPLE2-4-B"
16: "SAMPLE2-10-2"
17: "SAMPLE2-14-2-Z"
18: "SAMPLE2-D-1-14"
19: "SAMPLE10-3-5"
Enter fullscreen mode Exit fullscreen mode

Is there more simple way?

I find this code verbose, so I'm looking for a simpler way.

For example, because these texts are like "SAMPLE[0-9]-[0-9|A-Z]-[0-9|A-Z]" or "SAMPLE[0-9]-[0-9|A-Z]-[0-9|A-Z]-[0-9|A-Z]", so I can split them with regex.

...
private compareWithSplit(a: string|null|undefined, b: string|null|undefined): number {
...
        const splittedA = a.match(/(([0-9]+)|([0-9|A-Z]+$))/g);
        const splittedB = b.match(/(([0-9]+)|([0-9|A-Z]+$))/g);
        if((splittedA == null || splittedA.length <= 0) ||
                (splittedB == null || splittedB.length <= 0)) {
            return a.localeCompare(b);
        }
        let index = 0;
        for(const sa of splittedA) {
            if(index >= splittedB.length) {
                return 1;
            }
            // to ignore compile error because the type is treated as string|undefined
            const sb = splittedB[index] ?? '';
            const na = parseInt(sa);
            const nb = parseInt(sb);
            if(isNaN(na)) {
                // sa: [A-Z]
                if(isNaN(nb)) {
                    // sb: [A-Z]
                    return sa.localeCompare(sb);
                }
                return 1;
            }
            if(isNaN(nb)) {
                // sa: [0-9] sb: [A-Z]
                return -1;
            }
            if((na - nb) !== 0) {
                return (na - nb);
            }
            // if na === nb, move next values.
            index += 1;
        }
        return (splittedA.length - splittedB.length);
    }
...
Enter fullscreen mode Exit fullscreen mode

But I think it's not so efficient.
If I found more better way, I will write in this post.

Top comments (0)