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

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);

Related

How to group and sum subarray items in PHP Laravel

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;
?>

Unsetting nested descendants of a particular ancestor based on an key and if empty unset the ancestor as well

Let's imagine we have this multidimensional array which contains nested arrays
$arr = [
1 => [
'id' => 1,
'families' => [
0 => [
'id' => 2
],
1 => [
'id' => 3
],
]
],
2 => [
'id' => 1,
'families' => [
0 => [
'id' => 2,
'products' => [
1 => 'John Doe'
],
],
1 => [
'id' => 3,
'products' => [],
],
]
],
3 => [
'id' => 1,
'products' => [
1 => 'Hi',
2 => 'Hello',
]
],
4 => [
'id' => 1,
'families' => [
0 => [
'id' => 2
],
1 => [
'id' => 3
],
]
],
];
I need to keep all ancestors and descendants where there is at least one item in the key "products", all other arrays should be unset.
So, in this particular example, the result should be as follows:
$arr = [
2 => [
'id' => 1,
'families' => [
0 => [
'id' => 2,
'products' => [
1 => 'John Doe'
],
],
]
],
3 => [
'id' => 1,
'products' => [
1 => 'Hi',
2 => 'Hello',
]
],
];
Basically, what needs to be done is to go from the most inner array up and asking:
1) Is our key "products" empty? Yes
2) Is our key "families" either empty or not set? Yes
3) unset this array
You can do it like this:
<?php
$arr = [
1 => [
'id' => 1,
'families' => [
0 => [
'id' => 2
],
1 => [
'id' => 3
],
]
],
2 => [
'id' => 1,
'families' => [
0 => [
'id' => 2,
'products' => [
1 => 'John Doe'
],
],
1 => [
'id' => 3,
'products' => [],
],
]
],
3 => [
'id' => 1,
'products' => [
1 => 'Hi',
2 => 'Hello',
]
],
4 => [
'id' => 1,
'families' => [
0 => [
'id' => 2
],
1 => [
'id' => 3
],
]
],
];
foreach($arr as $k => $a) {
if(strpos(json_encode($a), "products") == 0){
unset($arr[$k]);
}
}
var_dump($arr);
https://3v4l.org/tNSqs

Php array transformation and combination

So I have array like this one
[
'custid' => [
'customer_number_1' => '20098374',
'customer_number_8' => '20098037',
'customer_number_15' => '20098297'
],
'destid' => [
'destination_numbers_1' => [
(int) 0 => '20024838',
(int) 1 => '20041339'
],
'destination_numbers_8' => [
(int) 0 => '20008293'
],
'destination_numbers_15' => [
(int) 0 => '20016969',
(int) 1 => '20022919',
(int) 2 => '20025815',
(int) 3 => '20026005',
(int) 4 => '20027083',
(int) 5 => '20045497'
]
]
]
Goal is to merge cust id with destid in pairs and should look like so
[
(int) 0 => [
'user_id' => (int) 1,
'sap_customer_id' => '20098374',
'sap_destination_id' => '20024838'
],
(int) 1 => [
'user_id' => (int) 1,
'sap_customer_id' => '20098374',
'sap_destination_id' => '20041339',
],
(int) 2 => [
'user_id' => (int) 1,
'sap_customer_id' => '20098037',
'sap_destination_id' => '20008293,
],
(int) 3 => [
'user_id' => (int) 1,
'sap_customer_id' => '20098297'
'sap_destination_id' => '20016969',
],
...
I have tried with code below, but I am getting destination_id number as array and duplicated customer numbers. Also I have tried with array walk but result is same.
$data = [];
foreach ($sap_data['custid'] as $custid) {
foreach ($sap_data['destid'] as $destid) {
$data[] = [
'user_id' => 1,
'sap_customer_id' => $custid,
'sap_destination_id' => $destid
];
}
}
Thx for helping!
You should make inner loop in other way
foreach($sap_data['custid'] as $k => $custid) {
// Make destination key
$dkey = str_replace('customer_number', 'destination_numbers', $k);
// And get array, for example, destination_numbers_1 for customer_number_1
foreach ($sap_data['destid'][$dkey] as $destid) {
$data[] = [
'user_id' => 1,
'sap_customer_id' => $custid,
'sap_destination_id' => $destid
];
}
}
demo on eval.in

How to generate a tree-like array from flat array without use of references

I'm stuck with transforming flat array to multidimensional tree like. I have already done it, but I used references, which creates another set of problems down the line, so I need to do this without references.
Input array:
Array
[
1 =>[
'content_id' => 1,
'sort_order' => 1,
'level' => 1
],
2 =>[
'content_id' => 7,
'sort_order' => 2,
'level' => 2
],
3 =>[
'content_id' => 4,
'sort_order' => 3,
'level' => 2
],
4 =>[
'content_id' => 2,
'sort_order' => 4,
'level' => 3
],
5 =>[
'content_id' => 3,
'sort_order' => 5,
'level' => 1
],
6 =>[
'content_id' => 6,
'sort_order' => 6,
'level' => 1
],
7 =>[
'content_id' => 5,
'sort_order' => 7,
'level' => 2
]
]
Output array:
1 => [
'id' = 1,
'visited' = 0,
'children' => [
2 => [
'id' => 7,
'visited' => 0,
'children' => []
],
3 => [
'id' => 4,
'visited' => 0,
'children' => [
4 => [
'id' = 2,
'visited' = 0,
'children' => []
]
]
],
5 => [
'id' => 3,
'visited' => 0,
'children' => []
],
6 => [
'id' => 6,
'visited' => 0,
'children' => [
7 => [
'id' => 5,
'visited' => 0,
'children => []
]
]
]
Any idea how to tackle a problem like this without having direct parent relation set? I can use recursion, but references are problem.
Oooh, this kind of problems do tickle my fancy. So, here is my solution:
<?php
$origArray = array(
array('content_id' => 1, 'sort_order' => 1, 'level' => 1),
array('content_id' => 7, 'sort_order' => 2, 'level' => 2),
array('content_id' => 4, 'sort_order' => 3, 'level' => 2),
array('content_id' => 2, 'sort_order' => 4, 'level' => 3),
array('content_id' => 3, 'sort_order' => 5, 'level' => 1),
array('content_id' => 6, 'sort_order' => 6, 'level' => 1),
array('content_id' => 5, 'sort_order' => 7, 'level' => 2),
);
function sortByOrder($a, $b) {
if ($a['sort_order'] == $b['sort_order']) {
return 0;
}
return ($a['sort_order'] < $b['sort_order']) ? -1 : 1;
}
function createHierarchicalArray($arr) {
$result = array();
foreach ($arr as $el) {
$result = insertArrayElement($result, $el['content_id'], $el['level']);
}
return $result;
}
function insertArrayElement($array, $id, $level, $currentLevel = 1) {
if ($level > $currentLevel) {
$ids = array_keys($array);
$currentId = end($ids);
if (!isset($array[$currentId]['children'])) {
$array[$currentId]['children'] = array();
}
$array[$currentId]['children'] = insertArrayElement($array[$currentId]['children'], $id, $level, $currentLevel + 1);
} else {
$array[$id] = array();
}
return $array;
}
// Could do without this, if the array is already sorted. Otherwise it's a necessary step.
uasort($origArray, 'sortByOrder');
$result = createHierarchicalArray($origArray);
var_dump($result);
Edit: Changed the code to incorporate the changes in the question.

PHP Merge by values in same array

So I have this array in PHP.
$arr = [
[ 'sections' => [1], 'id' => 1 ],
[ 'sections' => [2], 'id' => 1 ],
[ 'sections' => [3], 'id' => NULL ],
[ 'sections' => [4], 'id' => 4 ],
[ 'sections' => [5], 'id' => 4 ],
[ 'sections' => [6], 'id' => 4 ]
];
I want to merge on 'id' and get something like
$arr = [
[ 'sections' => [1, 2], 'id' => 1 ],
[ 'sections' => [3], 'id' => NULL ],
[ 'sections' => [4, 5, 6], 'id' => 4 ]
];
Just struggling to get my head around this one. Any Ideas
I've created this quick function that might work for you
<?php
// Your array
$arr = array(
array( 'elem1' => 1, 'elem2' => 1 ),
array( 'elem1' => 2, 'elem2' => 1 ),
array( 'elem1' => 3, 'elem2' => NULL ),
array( 'elem1' => 4, 'elem2' => 4 ),
array( 'elem1' => 5, 'elem2' => 4 ),
array( 'elem1' => 6, 'elem2' => 4 )
);
print_r($arr);
function mergeBy($arr, $elem2 = 'elem2') {
$result = array();
foreach ($arr as $item) {
if (empty($result[$item[$elem2]])) {
// for new items (elem2), just add it in with index of elem2's value to start
$result[$item[$elem2]] = $item;
} else {
// for non-new items (elem2) merge any other values (elem1)
foreach ($item as $key => $val) {
if ($key != $elem2) {
// cast elem1's as arrays, just incase you were lazy like me in the declaration of the array
$result[$item[$elem2]][$key] = $result[$item[$elem2]][$key] = array_merge((array)$result[$item[$elem2]][$key],(array)$val);
}
}
}
}
// strip out the keys so that you dont have the elem2's values all over the place
return array_values($result);
}
print_r(mergeBy($arr));
?>
Hopefully it'll work for more than 2 elements, and you can choose what to sort on also....

Categories