How to group and sum subarray items in PHP Laravel - php

I have N arrays. with n grade_items subarrays.
just like this.
array:2 [
0 => array:10 [
"id" => 9
"course_id" => 6
"semester_id" => 2
"name" => "Assignment"
"total_score" => 10
"grade_items" => array:1 [
0 => array:7 [
"id" => 5
"gradelist_id" => 9
"student_course_id" => 11
"score" => 8
"created_at" => "2020-04-21T03:31:20.000000Z"
"updated_at" => "2020-04-21T20:04:10.000000Z"
]
]
]
1 => array:10 [
"id" => 10
"course_id" => 6
"semester_id" => 2
"name" => "Pop Quiz"
"total_score" => 20
"grade_items" => array:1 [
0 => array:7 [
"id" => 6
"gradelist_id" => 10
"student_course_id" => 11
"score" => null
"created_at" => "2020-04-22T00:11:17.000000Z"
"updated_at" => "2020-04-22T00:11:17.000000Z"
]
]
]
]
I am trying to add each grade_item subarray from each array where the student_course_id is the same. Where there is only one grade_item and no other one with the same student_course_id, then it returns just that one value instead of a sum.
I have gone through this thread
But it just messed up the logic in my head further. I've been at this for weeks.
When I add the scores from each grade_item, i want to put that value into another model say "result_model" that would look like:
result_item [
"id" => 1,
"student_course_id" => 11,
"score" => 15 //total of grade_items from all arrays where the student_course_id's were the same
];
Help!

So basically you want to regroup the current information to receive the sum of grades. It seems the the information comes form a databases, so why don't you GROUP BY and sum on database level?
Anyway. Here's an approach. Start by keeping a map of student_course_id => score. First it will be empty: $map = [];
Then start iterating through the whole structure e.g. foreach ($data as $row. For each row, you need to check all the corresponding grade_items e.g. foreach ($row['grade_items'] as $gradeItem). Now you need to check whether the student_course_id from the grade item is present into the mapping.
If it's not present, create it with starting value of zero e.g.
if (!key_exists($gradeItem['student_course_id'], $map)) {
$map[$gradeItem['student_course_id']] = 0;
}
Once you ensure that the student_course_id is present, you can just add to the previous value the current score => $map[$gradeItem['student_course_id']] += $gradeItem['score'].

Here is a sample data i used
$array = [
[
'id' => 9,
'course_id' => 6,
'semester_id' => 2,
'name' => 'Assignment',
'total_score' => 10,
'grade_items' => [
[
'id' => 5,
'gradelist_id' => 9,
'student_course_id' => 11,
'score' => 8,
'created_at' => '2020-04-21T03:31:20.000000Z',
'updated_at' => '2020-04-21T20:04:10.000000Z',
],
[
'id' => 5,
'gradelist_id' => 9,
'student_course_id' => 15,
'score' => 15,
'created_at' => '2020-04-21T03:31:20.000000Z',
'updated_at' => '2020-04-21T20:04:10.000000Z',
]
]
],
[
'id' => 10,
'course_id' => 6,
'semester_id' => 2,
'name' => 'Pop Quiz',
'total_score' => 20,
'grade_items' => [
[
'id' => 6,
'gradelist_id' => 10,
'student_course_id' => 11,
'score' => 21,
'created_at' => '2020-04-22T00:11:17.000000Z',
'updated_at' => '2020-04-22T00:11:17.000000Z',
],
[
'id' => 6,
'gradelist_id' => 10,
'student_course_id' => 23,
'score' => 15,
'created_at' => '2020-04-22T00:11:17.000000Z',
'updated_at' => '2020-04-22T00:11:17.000000Z',
]
]
]
];
and here is the code;
$id = 0;
return collect($array)
->flatMap(function ($item){
return $item['grade_items'];
})
->groupBy('student_course_id')
->transform(function ($subItems, $courseId) use (&$id) {
$id++;
return [
'id' => $id,
'student_course_id' => $courseId,
'score' => $subItems->sum('score')
];
})
->values()
->toArray();
here is the result;
[
[
'id' => 1,
'student_course_id' => 11,
'score' => 29,
],
[
'id' => 2,
'student_course_id' => 15,
'score' => 15,
],
[
'id' => 3,
'student_course_id' => 23,
'score' => 15,
]
]

Use the sum() function. You can loop through the array, and do any checks you need, like if it is not Null etc, and pluck() it and then sum() it.

May I suggest recursivity approach?
<?php
function rec_sum_grades(&$array_grades, &$sum = 0){
$sum += $array_grades['total_score'];
if(!empty($array_grades['grade_items'])){
$this->rec_sum_grades($array_grades['grade_items'], $sum);
}
}
rec_sum_grades($array_grades, $sum);
echo $sum;
?>

Related

PHP - Sort array by subarray value based on a second array

I've done some research including going through PHP Sort Array By SubArray Value but this is a variation:
I have an array as follows:
$data =
[
0 => [
'id' => (int) 5,
'name' => 'Foo',
'group_id' => (int) 1,
],
1 => [
'id' => (int) 6,
'name' => 'Bar',
'group_id' => (int) 1,
],
2 => [
'id' => (int) 8,
'name' => 'Baz',
'group_id' => (int) 7,
],
3 => [
'id' => (int) 9,
'name' => 'ABC',
'group_id' => (int) 2,
],
4 => [
'id' => (int) 10,
'name' => 'DEF',
'group_id' => (int) 65,
]
];
I also have an array second array of group_id's that are relevant to a search a user has performed:
$gids = [7, 65];
What I want to do is order $data by group_id based on the values inside $gids. So the order of $data should become:
0 => [
'id' => (int) 8,
'name' => 'Baz',
'group_id' => (int) 7,
],
1 => [
'id' => (int) 10,
'name' => 'DEF',
'group_id' => (int) 65,
]
2 => [
'id' => (int) 5,
'name' => 'Foo',
'group_id' => (int) 1,
],
3 => [
'id' => (int) 6,
'name' => 'Bar',
'group_id' => (int) 1,
],
4 => [
'id' => (int) 9,
'name' => 'ABC',
'group_id' => (int) 2,
],
Note that once the $gids array has been taken into account, the group_id of the remaining items in $data is numerical (ascending order in this case: 1, 1, 2).
I understand it's possible to use usort and an anonymous function, e.g.
usort($data, function ($gids) {
});
However, I don't understand what to write inside this function to perform ordering in this way? My initial thought was to do a foreach($gids) followed by a foreach($data) and compare the group_id value. But I don't know what to do in terms of modifying $data so that it reorders.
Please can someone help?
Equally, if there is already a post which explains how to do this please let me know, because I couldn't find one on here. The original link simply bases ordering numerically, not on a second array ($gids equivalent).
Using PHP 7.1.0
This should do the trick
usort($data, function($a, $b) use ($gids)
{
$posA = array_search($a['group_id'], $gids);
if ($posA === false) {
$posA = 9999;
}
$posB = array_search($b['group_id'], $gids);
if ($posB === false) {
$posB = 9999;
}
return $posA - $posB;
});
If found in the array of $gids, the sorting of $gids will be used, else the elements will stay in the order they're given in.
The return I'm getting is the following:
array:5 [▼ 0 => array:3 [▼
"id" => 8
"name" => "Baz"
"group_id" => 7 ] 1 => array:3 [▼
"id" => 10
"name" => "DEF"
"group_id" => 65 ] 2 => array:3 [▼
"id" => 5
"name" => "Foo"
"group_id" => 1 ] 3 => array:3 [▼
"id" => 6
"name" => "Bar"
"group_id" => 1 ] 4 => array:3 [▼
"id" => 9
"name" => "ABC"
"group_id" => 2 ] ]

Multidimensional Array Sorting/Chaining Based on Value PHP

I have an array below format:
$data = [
'2018-04-26' => [
[
'op' => 3,
'cl' => 4
],
[
'op' => 3,
'cl' => 2
],
[
'op' => 4,
'cl' => 3
]
]
];
I want to make it sort as
$data['2018-04-26'] = [
[
'op' => 3,
'cl' => 4
],
[
'op' => 4,
'cl' => 3
],
[
'op' => 3,
'cl' => 2
]
];
How will I sort based on OP, CL.
OP of current array is equal to previous array CL. or
CL of current array is equal to next array OP.
We can start any where but let start accordingly index 0-n.
There is no chance for multiple solution.
If same op/cl appeared we can put it anywhere.
I have tried using usort() but how do I put the logic.
function cmpare($a, $b){
//the logic
return 0;
}
usort($data['2018-04-26'], 'cmpare');
This is the php code:
$data = [];
$data['2018-04-26'][] = [
'op' => 3,
'cl' => 4
];
$data['2018-04-26'][] = [
'op' => 4,
'cl' => 3
];
$data['2018-04-26'][] = [
'op' => 3,
'cl' => 2
];
usort($data['2018-04-26'], function($a, $b){
return ($a['op'] == $b["cl"]) ? 1 : 0;
});

Searching an array In Laravel

I been getting two arrays and merging them and then looking for a certain value under name but can't seem to get it working
$temp = [
['id' => 3, 'name' => 'Taylor'],
['id' => 3, 'name' => 'Abigail'],
];
$temp1 = [
['id' => 3, 'name' => 'Taylor'],
['id' => 3, 'name' => 'Taylor'],
];
$ggg = array_merge($temp,$temp1);
Use the contains() method of a collection. The schema:
collect($array1) // Create a collection from the first array
->merge($array2) // merge it with the second array
->contains($key, $value); // returns boolean
Your code:
$temp = [
['id' => 3, 'name' => 'Taylor'],
['id' => 3, 'name' => 'Abigail'],
];
$temp1 = [
['id' => 3, 'name' => 'Taylor'],
['id' => 3, 'name' => 'Taylor'],
];
collect($temp)->merge($temp1)->contains('name', 'Taylor'); // true
collect($temp)->merge($temp1)->contains('name', 'Jane'); // false
If you want to get the items who are matching the criteria, use where():
$result = collect($temp)->merge($temp1)->where('name', 'Taylor');
This will return:
Collection {#465 ▼
#items: array:3 [▼
0 => array:2 [▼
"id" => 3
"name" => "Taylor"
]
2 => array:2 [▼
"id" => 3
"name" => "Taylor"
]
3 => array:2 [▼
"id" => 3
"name" => "Taylor"
]
]
}
Use print_r($ggg) to print the resulting array. Referring to the name part you should use $ggg[0]['name'] for example. Not sure what is your final goal for merging the 2 arrays, do you want an array with all 4 elements ? The way you are doing it you will end up with only 2 elements, index 0 of $temp1 will overwrite index 0 of $temp, same thing with index 1.
I have tried and maybe this will help you.
$temp = [
['id' => 3, 'name' => 'Taylor'],
['id' => 3, 'name' => 'Abigail'],
];
$temp1 = [
['id' => 3, 'name' => 'Taylor'],
['id' => 3, 'name' => 'Taylor'],
];
$ggg = array_merge($temp,$temp1);
foreach ($ggg as $key) {
echo "ID: ".$key['id']."<br>";
echo "NAME: ".$key['name']."<br>";
}
I think you want something like below. Since you tagged this as Laravel I've done it with laravel specific code, but it would be possible to use native PHP too.
$temp = [
['id' => 3, 'name' => 'Taylor'],
['id' => 3, 'name' => 'Abigail'],
];
$temp1 = [
['id' => 3, 'name' => 'Taylor'],
['id' => 3, 'name' => 'Taylor'],
];
$item = collect($temp)->merge($temp1)->first(function ($item) {
return $item['name'] == 'Taylor';
});

Finding the lowest value for specific key in a deeply nested array

Given an array with the following structure:
$orders = [
0 => [
'volume' => 1926,
'lowPrice' => 1000.00,
'date' => 2016-12-29 00:45:23
],
1 => [
'volume' => 145,
'lowPrice' => 1009.99,
'date' => 2016-12-31 14:23:51
],
2 => [
'volume' => 19,
'lowPrice' => 985.99,
'date' => 2016-12-21 09:37:13
],
3 => [
'volume' => 2984,
'lowPrice' => 749.99,
'date' => 2017-01-01 19:37:22
],
// ...
]
I'm looking for the most performant way to find the lowest value for lowPrice. The size of $orders will more than likely be larger than 500 items. For the given array, the result would be as follows:
$lowestPrice = $this->findLowestPrice($orders);
echo $lowestPrice; // 749.99
Extract an array of lowPrice and find the min():
echo min(array_column($orders, 'lowPrice'));
Since those appear to be floats, you'll probably want number_format() or money_format().
If you're only interested in the value of the lowest price (not the index in the array or something) you can use array_reduce:
$orders = [
0 => [
'volume' => 1926,
'lowPrice' => 1000.00,
'date' => "2016-12-29 00:45:23"
],
1 => [
'volume' => 145,
'lowPrice' => 1009.99,
'date' => "2016-12-31 14:23:51"
],
2 => [
'volume' => 19,
'lowPrice' => 985.99,
'date' => "2016-12-21 09:37:13"
],
3 => [
'volume' => 2984,
'lowPrice' => 749.99,
'date' => "2017-01-01 19:37:22"
],
];
$lowestPrice = array_reduce(
$orders,
function($c, $v)
{
if ($c === null)
return $v["lowPrice"];
return $v["lowPrice"] < $c ? $v["lowPrice"] : $c;
}
);

Recursive count of children in nested arrays + changing values by reference

I know this is basic recursion but I get stuck anyway :(
I need to count how many elements each element has below it (children, grandchildren,...) and write that value into original array.
My example array:
$table = [
1 => [
'id' => 1,
'children_count' => 0
],
2 => [
'id' => 2,
'children_count' => 0,
'children' => [
3 => [
'id' => 3,
'children_count' => 0,
'children' => [
4 => [
'id' => 4,
'children_count' => 0,
'children' => [
5 => [
'id' => 5,
'children_count' => 0
],
6 => [
'id' => 6,
'children_count' => 0
]
]
]
]
]
]
]
];
My recursion code:
function count_children(&$array){
foreach($array['children'] as &$child){
if(isset($child['children'])){
$array['children_count'] += count_children($child);
}
else return 1;
}
}
Call for recursion:
//call for each root element
foreach($table as &$element){
if(isset($element['children'])) count_children($element);
}
Expected output:
$table = [
1 => [
'id' => 1,
'children_count' => 0
],
2 => [
'id' => 2,
'children_count' => 4,
'children' => [
3 => [
'id' => 3,
'children_count' => 3,
'children' => [
4 => [
'id' => 4,
'children_count' => 2,
'children' => [
5 => [
'id' => 5,
'children_count' => 0
],
6 => [
'id' => 6,
'children_count' => 0
]
]
]
]
]
]
]
];
Where did I got it wrong?
My function does something, element 3 gets value 1, but thats about it.
Here is the ideone link: http://ideone.com/LOnl3G
function count_children(&$table){
$count1 = 0;
foreach($table as &$array) {
$count = 0;
if (isset($array['children'])) {
$count += count($array['children']);
$count += count_children($array['children']);
}
$array['children_count'] = $count;
$count1 += $count;
}
return $count1;
}
count_children($table);
print_r($table);

Categories