Sort By Alphabet then Numbers Laravel Collection - php

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?

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']);
});

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.

Related

How can I manage some irregular array to regular array?

I write code with some array that have different structure, but I must extract the data to do something else. How can I manager these array?
The array's structure are as follow:
$a = [
'pos1' => 'somedata',
'pos2' => ['data2', 'data3'],
'pos3' => '';
];
$b = [
[
'pos1' => ['data1', 'data2', ['nest1', 'nest2']],
'pos2' => ['data1', 'data2', 'data3'],
],
['data1', 'data2'],
'data4',
];
The array's Index can be a key or a position, and the value of the corresponding index may be a array with the same structure. More tough problem is that the subarray can be nesting, and the time of the nesting has different length.
Fortunately, every array has it's owe fixed structure.
I want to convert the these array to the format as follow. When the index is a value, change it to the keyword; and if the index is a keyword, nothing changed.
$a = [
'pos1' => 'somedata',
'pos2' => [
'pos2_1' => 'data2',
'pos2_2' => 'data3'
],
'pos3' => '';
];
$b = [
'pos1' => [
'pos1_1' => [
'pos1_1_1' => 'data1',
'pos1_1_2' => 'data2',
'pos1_1_3' => [
'pos1_1_3_1' => 'nest1',
'pos1_1_3_2' => 'nest2',
],
],
'pos1_2' => [
'pos1_2_1' => 'data1',
'pos1_2_2' => 'data2',
'pos1_2_3' => 'data3',
],
],
'pos2' => ['data1', 'data2'],
'pos3' => 'data4',
];
My first solution is for every array, write the function to convert the format(the keyword will specify in function). But it is a huge task and diffcult to manage.
The second solution is write a common function, with two argument: the source array and the configuration that specify the keyword to correspondent value index. For example:
$a = [0, ['pos10' => 1]];
$conf = [
// It means that when the value index is 0, it will change it into 'pos1'
'pos1' => 0,
'pos2' => 1,
];
The common funciton will generate the result of:
$result = [
'pos1' => 0,
'pos2' => ['pos10' => 1],
]
But this solution will lead to a problem: the config is diffcult to understand and design, and other people will spend a lot of time to understand the format after conversion.
Is there are some better solution to manage these array that other people can easy to use these array?
Thanks.

Merging and array of arrays with the same keys

So I found an answer which is supposed to work, however it doesn't appear too...
Already accepted answer with the same issue.
I have got the following array called $banners:
[
0 => [
"bannerCustomTemplate" => 0,
"bannerId" => 1,
"bannerType" => 1,
"bannerTitle" => "Merry",
"bannerStrapline" => "Christmas",
"bannerPeriod" => "2018-12-01 to 2018-12-10",
"bannerText" => "Christmas opening hours"
],
1 => [
"bannerCustomTemplate" => 0,
"bannerId" => 7,
"bannerType" => 2,
"bannerTitle" => "Easter",
"bannerStrapline" => "Test",
"bannerPeriod" => "2018-12-04 to 2018-12-12",
"bannerText" => "dsadasdaas"
]
]
The answers I have read suggest $all_banners = call_user_func_array('array_merge', $banners);.
However this is giving me:
[
"bannerCustomTemplate" => 0,
"bannerId" => 7,
"bannerType" => 2,
"bannerTitle" => "Easter",
"bannerStrapline" => "Test",
"bannerPeriod" => "2018-12-04 to 2018-12-12",
"bannerText" => "dsadasdaas"
]
Seems like it's just replacing rather than merging. Anyone got any ideas?
Edit
Just read the following comment
Little note here. The updated variant with unpacking array doesn't work with string keys. But the first one works perfect. Just keep in mind this. – Alliswell
So I have now updated my code with another solutions, with the same results.
Edit 2
Well, merging is merging not replacing. So what I expect is:
[
"bannerCustomTemplate" => [ 0, 0 ],
"bannerId" => [ 1, 7 ],
"bannerType" => [ 1, 2 ],
"bannerTitle" => [ "Merry", "Easter" ]
"bannerStrapline" => [ "Christmas", "Test" ]
"bannerPeriod" => [ "2018-12-01 to 2018-12-10", "2018-12-04 to 2018-12-12" ]
"bannerText" => ["Christmas opening hours", "dsadasdaas" ]
]
If all your arrays are known to contain the same keys in the same order, this is probably easiest:
$data = [
['foo' => 'bar', 'baz' => 42],
['foo' => 'baz', 'baz' => 69]
];
$result = array_combine(array_keys($data[0]), array_map(null, ...$data));
This uses the useful behaviour of array_map with null as the callback to take one element from each input array and return a new combined array.

Grouping element array php based first character value

I have an array based MySql database.
This is the array.
[
0 => [
'id' => '1997'
'lokasi_terakhir' => 'YA4121'
]
1 => [
'id' => '1998'
'lokasi_terakhir' => 'PL2115'
]
2 => [
'id' => '1999'
'lokasi_terakhir' => 'PL4111'
]
]
How can I get the element lokasi_terakhir that grouped by the first character ? What the best way ?
This is the goal :
[
"Y" => 1,
"P" => 2
]
Please advise
Here are two refined methods. Which one you choose will come down to your personal preference (you won't find better methods).
In the first, I am iterating the array, declaring the first character of the lokasi_terakhir value as the key in the $result declaration. If the key doesn't yet exist in the output array then it must be declared / set to 1. After it has been instantiated, it can then be incremented -- I am using "pre-incrementation".
The second method first maps a new array using the first character of the lokasi_terakhir value from each subarray, then counts each occurrence of each letter.
(Demonstrations Link)
Method #1: (foreach)
foreach($array as $item){
if(!isset($result[$item['lokasi_terakhir'][0]])){
$result[$item['lokasi_terakhir'][0]]=1; // instantiate
}else{
++$result[$item['lokasi_terakhir'][0]]; // increment
}
}
var_export($result);
Method #2: (functional)
var_export(array_count_values(array_map(function($a){return $a['lokasi_terakhir'][0];},$array)));
// generate array of single-character elements, then count occurrences
Output: (from either)
array (
'Y' => 1,
'P' => 2,
)
You can group those items like this:
$array = [
0 => [
'id' => '1997',
'lokasi_terakhir' => 'YA4121'
],
1 => [
'id' => '1998',
'lokasi_terakhir' => 'PL2115'
],
2 => [
'id' => '1999',
'lokasi_terakhir' => 'PL4111'
]
];
$result = array();
foreach($array as $item) {
$char = substr($item['lokasi_terakhir'], 0, 1);
if(!isset($result[$char])) {
$result[$char] = array();
}
$result[$char][] = $item;
}
<?php
$array=[
0 => [
'id' => '1997',
'lokasi_terakhir' => 'YA4121'
],
1 => [
'id' => '1998',
'lokasi_terakhir' => 'PL2115'
],
2 => [
'id' => '1999',
'lokasi_terakhir' => 'PL4111'
]
];
foreach($array as $row){
$newArray[]=$row['lokasi_terakhir'][0];
}
print_r(array_flip(array_unique($newArray)));
this code gets the first letter of the fields lokasi_terakhir , get the unique values to avoid duplicates and just flips the array to get the outcome you want.
The output is this :
Array ( [Y] => 0 [P] => 1 )

PHP Append array in nested array

I have below array, I need to append a new array inside $newData['_embedded']['settings']['web/vacation/filters']['data'], How can I access and append inside it ?
$newData = [
"id" => "47964173",
"email" => "abced#gmail.com",
"firstName" => "Muhammad",
"lastName" => "Taqi",
"type" => "employee",
"_embedded" => [
"settings" => [
[
"alias" => "web/essentials",
"data" => [],
"dateUpdated" => "2017-08-16T08:54:11Z"
],
[
"alias" => "web/personalization",
"data" => [],
"dateUpdated" => "2016-07-14T10:31:46Z"
],
[
"alias" => "wizard/login",
"data" => [],
"dateUpdated" => "2016-09-26T07:56:43Z"
],
[
"alias" => "web/vacation/filters",
"data" => [
"test" => [
"type" => "teams",
"value" => [
0 => "09b285ec-7687-fc95-2630-82d321764ea7",
1 => "0bf117b4-668b-a9da-72d4-66407be64a56",
2 => "16f30bfb-060b-360f-168e-1ddff04ef5cd"
],
],
"multiple teams" => [
"type" => "teams",
"value" => [
0 => "359c0f53-c9c3-3f88-87e3-aa9ec2748313"
]
]
],
"dateUpdated" => "2017-07-03T09:10:36Z"
],
[
"alias" => "web/vacation/state",
"data" => [],
"dateUpdated" => "2016-12-08T06:58:57Z"
]
]
]
];
$newData['_embedded']['settings']['web/vacation/filters']['data'] = $newArray;
Any Hint to quickly append it, I don't want to loop-in and check for keys inside loops.
The settings subarray is "indexed". You first need to search the alias column of the subarray for web/vacation/filters to find the correct index. Using a foreach loop without a break will mean your code will continue to iterate even after the index is found (bad coding practice).
There is a cleaner way that avoids a loop & condition & break, use array_search(array_column()). It will seek your associative element, return the index, and immediately stop seeking.
You can use the + operator to add the new data to the subarray. This avoids calling a function like array_merge().
Code: (Demo)
if(($index=array_search('web/vacation/filters',array_column($newData['_embedded']['settings'],'alias')))!==false){
$newData['_embedded']['settings'][$index]['data']+=$newArray;
}
var_export($newData);
Perhaps a more considered process would be to force the insert of the new data when the search returns no match, rather than just flagging the process as unsuccessful. You may have to tweak the date generation for your specific timezone or whatever... (Demo Link)
$newArray=["test2"=>[
"type" =>"teams2",
"value" => [
0 => "09b285ec-7687-fc95-2630-82d321764ea7",
1 => "0bf117b4-668b-a9da-72d4-66407be64a56",
2 => "16f30bfb-060b-360f-168e-1ddff04ef5cd"
],
]
];
if(($index=array_search('web/vacation/filters',array_column($newData['_embedded']['settings'],'alias')))!==false){
//echo $index;
$newData['_embedded']['settings'][$index]['data']+=$newArray;
}else{
//echo "couldn't find index, inserting new subarray";
$dt = new DateTime();
$dt->setTimeZone(new DateTimeZone('UTC')); // or whatever you are using
$stamp=$dt->format('Y-m-d\TH-i-s\Z');
$newData['_embedded']['settings'][]=[
"alias" => "web/vacation/filters",
"data" => $newArray,
"dateUpdated" => $stamp
];
}
You need to find the key that corresponds to web/vacation/filters. For Example you could use this.
foreach ($newData['_embedded']['settings'] as $key => $value) {
if ($value["alias"]==='web/vacation/filters') {
$indexOfWVF = $key;
}
}
$newData['_embedded']['settings'][$indexOfWVF]['data'][] = $newArray;
From the comments. Then you want to merge the arrays. Not append them.
$newData['_embedded']['settings'][$indexOfWVF]['data'] = array_merge($newData['_embedded']['settings'][$indexOfWVF]['data'],$newArray);
Or (if it's always Filter1):
$newData['_embedded']['settings'][$indexOfWVF]['data']['Filter1'] = $newArray['Filter1'];

Elements in array without keys mixed with elements with keys

I've stumbled upon this function today:
public function rules()
{
return [
['status', 'default', 'value' => self::STATUS_ACTIVE],
['status', 'in', 'range' => [self::STATUS_ACTIVE, self::STATUS_DELETED]],
];
}
I don't understand this construction:
['status', 'default', 'value' => self::STATUS_ACTIVE]
How's that first two entries have only value, and the third has a key and a value. Is it something that PHP language allows?
This is nothing new. Key is just optional. You can find a similar case in the very first example of the PHP documentation for arrays.
Here it is.
<?php
$fruits = array (
"fruits" => array("a" => "orange", "b" => "banana", "c" => "apple"),
"numbers" => array(1, 2, 3, 4, 5, 6),
"holes" => array("first", 5 => "second", "third")
);
?>
http://php.net/manual/en/function.array.php

Categories