MAY
MAY

Reputation: 709

Sort By Alphabet then Numbers Laravel Collection

I am looking for a way to sort the collection in such a way that name values starting with the alphabet comes at the top and then name values that start with numbers. For example:

$collection = collect([
    ['name' => 'b', 'symbol' => '#'],
    ['name' => '2a', 'symbol' => '$'],
    ['name' => '1', 'symbol' => '@'],
    ['name' => 'a', 'symbol' => '%']
]);

The above collection should be sorted like this:

[
    [
        "name" => "a",
        "symbol" => "%",
    ],
    [
        "name" => "b",
        "symbol" => "#",
    ],
    [
        "name" => "1",
        "symbol" => "@",
    ],
    [
        "name" => "2a",
        "symbol" => "$",
    ],
]

But this is what I get when I sort it using sortBy method:

$collection->sortBy('name')->values()->all();
[
    [
        "name" => "1",
        "symbol" => "@",
    ],
    [
        "name" => "2a",
        "symbol" => "$",
    ],
    [
        "name" => "a",
        "symbol" => "%",
    ],
    [
        "name" => "b",
        "symbol" => "#",
    ],
]

Any idea how to sort this collection so that names starting with letters come first?

Upvotes: 1

Views: 1223

Answers (2)

mickmackusa
mickmackusa

Reputation: 47934

You want purely alphabetical name values to have top priority, then I assume natural sorting so that, say a2 comes before a10. Just write two rules in a custom callback in a sort() method call.

False evaluations are ordered before true evaluations when sorting ASC, so merely write the $b element before the $a element to sort DESC. To break any ties on the first comparison, call strnatcmp().

Laravel adopted arrow function syntax back in 2019.

Code: (Basic PHP Demo)

$collection->sort(fn($a, $b) =>
    (ctype_alpha($b['name']) <=> ctype_alpha($a['name']))
    ?: strnatcmp($a['name'], $b['name'])
);

If you, more specifically only want to check if the first character is a letter, you can use $a['name'][0] and $b['name'][0]. If the strings might have a multi-byte first character then a regex approach might be best.

Upvotes: 0

nice_dev
nice_dev

Reputation: 17805

You need to define your own custom comparator function to sort these collection objects using sort.

Compare both names by checking they are all alphabets. If both are alphabets, then usual string comparison using strcasecmp shall suffice. If either of them is an alphabet, push them to higher ranks by returning value -1, meaning to be placed above in the sorted order. If both are numerical or alphanumeric, use strcasecmp again.

<?php

$collection = collect([
    ['name' => 'b', 'symbol' => '#'],
    ['name' => '2a', 'symbol' => '$'],
    ['name' => '1', 'symbol' => '@'],
    ['name' => 'a', 'symbol' => '%']
]);

$collection = $collection->sort(function($a,$b){
    $a_is_alphabet = preg_match('/^[a-zA-Z]+$/', $a['name']) === 1;
    $b_is_alphabet = preg_match('/^[a-zA-Z]+$/', $b['name']) === 1;

    if($a_is_alphabet && $b_is_alphabet){
        return strcasecmp($a['name'], $b['name']);
    }elseif($a_is_alphabet){
        return -1;
    }elseif($b_is_alphabet){
        return 1;
    }

    return strcasecmp($a['name'], $b['name']);
});

Upvotes: 2

Related Questions