Calculate values of deep nested multi dimensional arrays in reverse - php

I need to calculate reward for employees in my company.
I have this structure (for example):
And in PHP it represents the following array:
<?php
$structure = [
"A" => [
"B" => [
"E" => [
"M" => null
],
"F" => [
"N" => [
"T" => null
],
"O" => null
],
"G" => [
"P" => null,
"Q" => [
"U" => null,
"V" => [
"X" => null,
"Y" => [
"3" => [
"4" => [
"4" => null,
"6" => null,
"7" => [
"8" => null
],
]
]
],
"Z" => null
]
]
],
"H" => null
],
"C" => [
"I" => null,
"J" => [
"R" => null
],
"K" => [
"S" => [
"W" => [
"1" => null,
"2" => null,
]
]
]
],
"D" => [
"L" => null
]
]
];
I need to calculate the reward for each employee. The end subordinates have reward only from their own work. But the seniors who have other subordinates have reward from their own work + works their subordinates.
For example:
Person A has own reward 10.
Person D has own reward 20.
Person L has own reward 15.
In the final,
Person L has final reward 15 (is final).
Person D has final reward 20 + 15 = 35 (D + L).
Person A has final reward 10 + 35 (A + D).
The calculation must be carried out below, however, the network can be arbitrarily deep. Calculating I would like to split into several parts. (For performance reasons)
The spider, which revises the structure to the appropriate format.
Calculate reward for each node.
Send information about each node via email.
I do not know how to proceed across the structure. Or to reorganize the structure to undergo easier. Can you think of anything?
I am grateful for you. Thanks!
#Martin
// EDIT: raw database
| id | parent | name
---------------------
| 1 | null | Martin
| 2 | null | Peter
| 3 | 1 | John
| 4 | 3 | Jack
// EDIT: new data structure:
[
"A" => [
"points" => 20,
"childs" => [
"B" => [
"points" => 10,
"childs" => [
"C" => [
"points" => 50,
"childs" => null
]
]
],
"D" => [
"points" => 30,
"childs" => [
"4" => [
"points" => 40,
"childs" => null
]
]
]
]
]
]

This can be done by traversing through the structure recursively from the inside-out, and stores the rewards for each employee it can find in a flattened 2D array.
It is dependent on each leaf of the structure having a starting value, as it needs a base value to calculate back up the tree on.
Using RecursiveIteratorIterator with the CHILD_FIRST flag allows you to loop through the array 'backwards' which is what we want in this case as that is where the starting rewards are. As we go through the tree, we obtain the subordinates rewards, add it to the current employee, and continue .. so by the time we get back to the top of the structure, we have calculated all employees.
Storing the result in a flattened array is then much easier to use and manipulate further along your logic.
Assumed starting structure:
// Each leaf has a value (random for example sake)
$structure = [
"A" => [
"B" => [
"E" => [
"M" => 10
],
"F" => [
"N" => [
"T" => 15
],
"O" => 5
],
"G" => [
"P" => 40,
"Q" => [
"U" => 30,
"V" => [
"X" => 35,
"Y" => [
"3" => [
"4" => [
"5" => 5,
"6" => 10,
"7" => [
"8" => 20
],
]
]
],
"Z" => 30
]
]
],
"H" => 15
],
"C" => [
"I" => 25,
"J" => [
"R" => 25
],
"K" => [
"S" => [
"W" => [
"1" => 40,
"2" => 50,
]
]
]
],
"D" => [
"L" => 15
]
]
];
Calculation:
// Iterate through the structure from the outside-in (child/leaves first)
$data = new RecursiveArrayIterator($structure);
$dataIt = new RecursiveIteratorIterator($data, RecursiveIteratorIterator::CHILD_FIRST);
$rewards = [];
foreach ($dataIt as $value) {
$subKeys = [];
$rewards[$dataIt->key()] ?? $rewards[$dataIt->key()] = 0; // Suppress any undefined index errors
if (is_array($value)) {
$subKeys = array_keys($value);
// traverse through all branches to obtain the existing reward values for subordinates
array_walk_recursive($value, function($reward, $person) use (&$subKeys) {
$subKeys[] = $person;
});
$subKeys = array_unique($subKeys);
foreach ($subKeys as $employee) {
$rewards[$dataIt->key()] += $rewards[$employee];
}
} else {
$rewards[$dataIt->key()] += $value;
}
}
print_r($rewards);
Return/final array:
Array
(
[M] => 10
[E] => 10
[T] => 15
[N] => 15
[O] => 5
[F] => 35
[P] => 40
[U] => 30
[X] => 35
[4] => 60
[6] => 10
[8] => 20
[7] => 20
[3] => 90
[Y] => 180
[Z] => 30
[V] => 335
[Q] => 520
[G] => 745
[H] => 15
[B] => 1060
[I] => 25
[R] => 25
[J] => 25
[1] => 40
[2] => 50
[W] => 90
[S] => 180
[K] => 270
[C] => 435
[L] => 15
[D] => 15
[A] => 1935
)
To get the number of employees under each node, you can do that easily in one line using count:
$employeesPersonA = count($structure['A'], COUNT_RECURSIVE); // 33
$employeesPersonC = count($structure['A']['C'], COUNT_RECURSIVE); // 8
$employeesPersonK = count($structure['A']['C']['K'], COUNT_RECURSIVE); // 4
EDIT:
For your database structure, you cannot get a multi dimensional result set out of the database so your only option there is to go through the result set and build your structure in PHP from that.

Here's one example, simplified to consider only nodes, 'A','B','C',D', & 'L'...
DROP TABLE IF EXISTS my_table;
CREATE TABLE my_table
(user CHAR(1) NOT NULL PRIMARY KEY
,lft INT NOT NULL
,rgt INT NOT NULL
);
INSERT INTO my_table VALUES
('A',1,10),
('B',2,3),
('C',4,5),
('D',6,9),
('L',7,8);
SELECT y.user
, GROUP_CONCAT(x.user ORDER BY x.lft) nodes
FROM my_table x
JOIN my_table y
ON x.lft BETWEEN y.lft AND y.rgt
GROUP
BY y.user
ORDER
BY y.lft;
+------+-----------+
| user | nodes |
+------+-----------+
| A | A,B,C,D,L |
| B | B |
| C | C |
| D | D,L |
| L | L |
+------+-----------+

Related

How to synchronize when updating records with many duplicate values

I'm doing an update function for a table with a many-to-many relationship, this is what I have:
tooth
- id
- name
cost
- id
- name
cost_tooth
- id
- tooth_id
- cost_id
- row_number
This is what I save in the intermediate table:
enter image description here
Column row_number is interpreted as row, row_number = 1 will be interpreted as row 1, row_number = 2 will be interpreted as row 2
Here's a picture of what I'm talking about
enter image description here
You will see what i say in the column TOOTH NAME
and now i want when i edit something in the column TOOTH NAME as below.
This is the value I get when I edit.
$output = [
0 => [
"tooth_id" => "1"
"row_number" => 1
]
1 => [
"tooth_id" => "2"
"row_number" => 1
]
2 => [
"tooth_id" => "1"
"row_number" => 2
]
3 => [
"tooth_id" => "2"
"row_number" => 2
]
4 => [
"tooth_id" => "3"
"row_number" => 2
]
5 => [
"tooth_id" => "4"
"row_number" => 2
]
]
$cost->tooths()->sync($output);
After I edited, the data in my database was messed up and not as I expected
enter image description here
Do you have a good solution? help me

PHP Calculate Unique combinations which total a specific value for multidimensional array

In an eCommerce store we have multiple transactions that relate to a specific order, e.g an order was made for 2 SKU's (1 transaction), 2 skus were added (another transaction), 1 was refunded (another transaction). The order itself is made up of multiple line items, each with a unique id.
Each of these transactions will have a numerical value, where together they will add up to the total of that order. What I'm trying to achieve is to take the line items and match their values to the transactions where there is no overlap (e.g a line item can only be part of one transaction).
A real world example would be as follows, there are 3 transactions -
Transaction 1 (6-671461867600) For 39.68
Transaction 2 (6-671717458000) For 31.98
Transaction 3 (6-671826772048) For -7.7
(these total 63.96)
And the line items which make up these transactions are as follows.
$lineItems = [
0 => [
"xId" => 96909066320
"price" => "-7.70"
"sku" => 'KB-291'
]
1 => [
"xId" => 4978455609424
"price" => "15.99"
"sku" => "261-R4-BE3B-CDWM"
]
2 => [
"xId" => 4978455642192
"price" => "15.99"
"sku" => "261-LV-5PT8-V6KA"
]
3 => [
"xId" => 4979504119888
"price" => "15.99"
"sku" => "261-UQ-3TEP-GJ8U"
]
4 => [
"xId" => 4979504152656
"price" => "15.99"
"sku" => "261-SO-58DE-0QKS"
]
5 => [
"xId" => 1887799246928
"price" => "7.70"
"sku" => 'KB-297'
]
];
What I'm trying to achieve is to specify which line items will make up each transaction, doing it manually I can find the follow work with no overlaps.
Transaction 1) 1887799246928, 4979504152656, 4979504119888
Transaction 2) 4978455642192, 4978455609424
Transaction 3) 96909066320
$array = [
"6-671826772048" => [
0 => "96909066320"
]
"6-671461867600" => [
0 => "1887799246928,4978455609424,4978455642192"
1 => "1887799246928,4978455609424,4979504119888"
2 => "1887799246928,4978455609424,4979504152656"
3 => "1887799246928,4978455642192,4979504119888"
4 => "1887799246928,4978455642192,4979504152656"
5 => "1887799246928,4979504119888,4979504152656"
]
"6-671717458000" => [
0 => "96909066320,1887799246928,4978455609424,4978455642192"
1 => "96909066320,1887799246928,4978455609424,4979504119888"
2 => "96909066320,1887799246928,4978455609424,4979504152656"
3 => "96909066320,1887799246928,4978455642192,4979504119888"
4 => "96909066320,1887799246928,4978455642192,4979504152656"
5 => "96909066320,1887799246928,4979504119888,4979504152656"
6 => "4978455609424,4978455642192"
7 => "4978455609424,4979504119888"
8 => "4978455609424,4979504152656"
9 => "4978455642192,4979504119888"
10 => "4978455642192,4979504152656"
11 => "4979504119888,4979504152656"
]
]
Where I've got to thus far is for every transaction, find what combination of line items would total the amount of the transaction - however I obviously need to ensure that there is no overlap,
e.g.
Transaction id 6-671826772048 can ONLY be calculated using Line Item 96909066320.
-Transaction id 6-671717458000 has an option of being calculated using 96909066320,1887799246928,4979504119888,4979504152656 - however, that would stop Transaction 1 from working.
To sumamrise, I want to take this array
$array = [
"6-671826772048" => [
0 => "96909066320"
]
"6-671461867600" => [
0 => "1887799246928,4978455609424,4978455642192"
1 => "1887799246928,4978455609424,4979504119888"
2 => "1887799246928,4978455609424,4979504152656"
3 => "1887799246928,4978455642192,4979504119888"
4 => "1887799246928,4978455642192,4979504152656"
5 => "1887799246928,4979504119888,4979504152656"
]
"6-671717458000" => [
0 => "96909066320,1887799246928,4978455609424,4978455642192"
1 => "96909066320,1887799246928,4978455609424,4979504119888"
2 => "96909066320,1887799246928,4978455609424,4979504152656"
3 => "96909066320,1887799246928,4978455642192,4979504119888"
4 => "96909066320,1887799246928,4978455642192,4979504152656"
5 => "96909066320,1887799246928,4979504119888,4979504152656"
6 => "4978455609424,4978455642192"
7 => "4978455609424,4979504119888"
8 => "4978455609424,4979504152656"
9 => "4978455642192,4979504119888"
10 => "4978455642192,4979504152656"
11 => "4979504119888,4979504152656"
]
]
and ONE result could be (although there are other correct possibilities)
$array = [
"6-671826772048" => "96909066320",
"6-671461867600 => "4978455642192, 4978455609424"
"6-671717458000" => "1887799246928, 4979504152656, 4979504119888"
];
If anyone has an idea on how to solve this it would be appreciated
Update - In the example I gave, whilst I presented one combination which solved the problem, there are many, there is no specific link between the transaction and line items, so as long as each transaction line items total the amount, and no transactions share line items, then the problem is considered solved
e.g the following would also be a valid solution
$array = [
"6-671826772048" => "96909066320",
"6-671461867600" => "4979504152656, 4979504119888"
"6-671717458000" => "4978455642192, 4978455609424, 1887799246928"
];

Laravel, How to store multiple open and close time per every day

I have array of days and their open time and close time as below, I need to store these days and the open and close time for a selected store in table, in the same time if the store have already entered in this table then update the current records so no duplicate will be found
I hope you can help me in this
many thanks
"store_id" => "625"
"day_id" => array:7 [
1 => "1"
2 => "2"
3 => "3"
4 => "4"
5 => "5"
6 => "6"
7 => "7"
]
"open_time" => array:7 [
1 => "13:20"
2 => "01:20"
3 => "13:20"
4 => "01:20"
5 => "15:50"
6 => "07:20"
7 => "23:20"
]
"close_time" => array:7 [
1 => "20:20"
2 => "08:20"
3 => "20:20"
4 => "21:20"
5 => "18:20"
6 => "00:20"
7 => "19:20"
]
I tried with this but need more
public function store(Request $request)
{
$store_id=$request->storeinfo_id;
foreach ($request->input('day_id') as $key => $val) {
$array = [
'day_id' => $val,
];
Storeday::where('storeinfo_id',$store_id)->create($array);
}
return response()->json(['data' => trans('message.success')]);
}

Sum values of array with same key and values

I'm trying to sum a value of an array with the same employee no.
Here's the example of the array I'm trying to sum.
0 => array:10 [▼
"employee_no" => "04052018"
"employee_id" => 317
"company_id" => 4
"name" => ""
"monthly_salary" => 14000.0
"daily_salary" => 537.0
"work_hours" => 8
"sss_deduction" => 0
"pagibig_deduction" => 100
"philhealth_deduction" => 192
and
0 => array:10 [▼
"employee_no" => "04052018"
"employee_id" => 317
"company_id" => 4
"name" => ""
"monthly_salary" => 14000.0
"daily_salary" => 537.0
"work_hours" => 8
"sss_deduction" => 0
"pagibig_deduction" => 100
"philhealth_deduction" => 192
they are duplicates array in which i want to get the sum of work hours only to have this result
0 => array:10 [▼
"employee_no" => "04052018"
"employee_id" => 317
"company_id" => 4
"name" => ""
"monthly_salary" => 14000.0
"daily_salary" => 537.0
"work_hours" => 16
"sss_deduction" => 0
"pagibig_deduction" => 100
"philhealth_deduction" => 192
There are about 24 and more occurrences if this array in my result set in which i want to merge as one summing up the total work hours
Tried this one but getting no luck
foreach ($employee_attendance as $row){
if($row['employee_id'] === $row['employee_id']){
$payroll_data['work_hours'] += $row['total_hours'];
}
$payroll[] = $payroll_data;
}
The best would be to get it sorted out from Eloquent queries itself. However, if you wish, you can also use Collections' methods such as groupBy and each to filter the values based on employee ID and do a sum on them. Finally, have a result variable to store all the results.
Snippet:
$result = [];
$arr = $arr->groupBy('employee_no')->each(function($item,$key) use (&$result){
$sum = $item->sum('work_hours');
$emp_data = $item->first();
$emp_data['work_hours'] = $sum;
$result[] = $emp_data;
});
Full Code:
<?php
$arr = collect([
[
"employee_no" => "04052018",
"employee_id" => 317,
"company_id" => 4,
"name" => "",
"monthly_salary" => 14000.0,
"daily_salary" => 537.0,
"work_hours" => 8,
"sss_deduction" => 0,
"pagibig_deduction" => 100,
"philhealth_deduction" => 192
],
[
"employee_no" => "04052018",
"employee_id" => 317,
"company_id" => 4,
"name" => "",
"monthly_salary" => 14000.0,
"daily_salary" => 537.0,
"work_hours" => 8,
"sss_deduction" => 0,
"pagibig_deduction" => 100,
"philhealth_deduction" => 192
],
[
"employee_no" => "04052019",
"employee_id" => 317,
"company_id" => 4,
"name" => "",
"monthly_salary" => 14000.0,
"daily_salary" => 537.0,
"work_hours" => 80,
"sss_deduction" => 0,
"pagibig_deduction" => 100,
"philhealth_deduction" => 192
]
]);
$result = [];
$arr = $arr->groupBy('employee_no')->each(function($item,$key) use (&$result){
$sum = $item->sum('work_hours');
$emp_data = $item->first();
$emp_data['work_hours'] = $sum;
$result[] = $emp_data;
});
print_r($result);
As what others said, it's better to group and sum those work hours when you retrieve it from the database.
But in case you still want to do it like what you explained above, it can be done like this although it might not be the best way
foreach ($employee_attendance as $row){
// Check if this is the first occurence of the employee number or not
if (!isset ($payroll_data[$row['employee_no']]['work_hours']) ) {
// First occurence will set the total hours to the current loop work hours
$payroll_data[$row['employee_no']]['work_hours'] = $row['work_hours'];
} else {
// Second occurence and so on will sum previous total work hours
$payroll_data[$row['employee_no']]['work_hours'] += $row['work_hours'];
}
}
And the $payroll_data structure will be like this:
[
"04052018" => [
"work_hours" => 16 // Total work hours of this employee number
],
// Repeat for other employee number
]
Hence, it's easier for you to check the employee number with their total working hours as well.

PHP- Sorting multidimensional array into another by range of numbers

I have a multidimensional array that I get from DB. Array has a number of views by each hour that is logged in the DB as a view, and it looks like this:
array:11 [▼
0 => array:2 [▼
"hour" => 0
"views" => 1
]
1 => array:2 [▼
"hour" => 1
"views" => 1
]
2 => array:2 [▼
"hour" => 4
"views" => 1
]
...and so on
]
I need to make a new array that will contain number of views for range of 2 hours. So for example from the array shown above I would like to get an array with, number of views for time between 0-2, that would 2, and for 2-4, would be 0 in this case, and so on.
You can do it in Mysql query:
select floor(hour/2) range, sum(views) sum
from thetable
group by range
You can just use a foreach to create a new array.
<?php
$your_array = [0 => [
"hour" => 0,
"views" => 4
],
1 => [
"hour" => 1,
"views" => 12
],
2 => [
"hour" => 4,
"views" => 1
],
3 => [
"hour" => 2,
"views" => 9
],
4 => [
"hour" => 21,
"views" => 19
]
];
foreach ($your_array as $value){
for($i=0;$i<=22;$i=$i+2){
$j=$i+2;
if($value['hour']>=$i && $value['hour']<$j){
isset($result[$i.'-'.$j])?$result[$i.'-'.$j]+=$value['views']:$result[$i.'-'.$j]=$value['views'];
}
}
}
print_r($result);
Try below code.
$arr = [0 => [
"hour" => 0,
"views" => 1
],
1 => [
"hour" => 1,
"views" => 1
],
2 => [
"hour" => 4,
"views" => 1
]];
foreach($arr as $row)
{
if($row['hour'] >= 0 && $row['hour'] <= 2)
{
$newArr['0-2'] = isset($newArr['0-2']) ? ($newArr['0-2'] + 1) : 1;
}
if($row['hour'] > 2 && $row['hour'] < 4)
{
$newArr['2-4'] = isset($newArr['2-4']) ? ($newArr['2-4'] + 1) : 1;
}
}
print_r($newArr);
Output
Array
(
[0-2] => 2
)

Categories