Reputation: 709
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
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
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