Sum array elements in two dimensional array with duplicate element value - php

I have a multi array that has some duplicated values that are same by payment_name ( payment_name is an element )
I want to sum quantity of each array that has same payment_name and display the amount as one. For now the sum comes as array of all amounts. Any assistance will be highly appreciated.
var_export($num) results looks like this
array(
'items' => array (
'2020-08-04' => array (
0 => array (
0 => array (
'payment_name' =>'Cash',
'amount_paid' => array (
0 => '0',
),
),
),
1 => 0,
),
'2020-08-05' => array (
0 => array(
0 => array (
'payment_name' => 'Cash',
'amount_paid' => array (
0 => '0',
1 => '0',
2 => '165',
),
),
1 => array (
'payment_name' => 'Mpesa',
'amount_paid' => array (
0 => '0',
1 => '0',
2 => '165',
),
),
),
1 => 165,
),
My expected array should be like this, the amount paid should be the total, not an array of all amounts:
"2020-08-05" => array:2 [▼
0 => array:2 [▼
0 => array:2 [▼
"payment_name" => "Cash"
"amount_paid" => "0"
]
1 => array:2 [▼
"payment_name" => "Mpesa"
"amount_paid" => "165"
]
]
]
1 => 165
]
My code is as below
$requests = $builder->get()->groupBy('date');
$num = $requests->map(function ($row) {
$result = array();
$ttl = $row->sum('amount_paid');
$row = $row->toArray();
$names = array_column($row, 'payment_name');
$amount = array_column($row, 'amount_paid');
$unique_modes = array_unique($names);
foreach ($unique_modes as $name) {
$this_keys = array_keys($names, $name);
$qty = array_sum((array_intersect_key($amount, array_combine($this_keys, $this_keys))));
$result[] = array("payment_name"=>$name, "amount_paid"=>$amount);
}
return [$result, $ttl ];
});
return $num;

I fixed my problem using the code below:
$num = $requests->map(function ($row) {
$result = array();
$ttl = $row->sum('amount_paid');
$row = $row->toArray();
$final_array = [];
foreach($row as $arr){
$final_array[$arr['payment_name']]['payment_name'] = $arr['payment_name'];
$final_array[$arr['payment_name']]['amount_paid'] = (isset($final_array[$arr['payment_name']]['amount_paid']))? $final_array[$arr['payment_name']]['amount_paid']+$arr['amount_paid'] : $arr['amount_paid'];
}
$final_array = array_values($final_array);
return [$final_array, $ttl];
});

Related

Explode and transpose nominated comma-separated strings of an associative array

I have an array with comma-separated values like below:
array:5 [
"manufacturer" => "BB"
"width" => "245,225, ..."
"height" => "45,65, ..."
"diameter" => "19,17, ..."
"type" => "AA"
]
There is no limit to how many comma-separated values there may be, but all 3 of them will have same length.
From this, I want to get transposed output like below:
[
245,
45,
19,
],
[
45,
65,
17
]
So far, I have tried following code.
$mandatoryFields = [
'width',
'height',
'diameter',
];
$finalArray = [];
for ($i = 0; $i < 2; $i+=1) {
foreach ($mandatoryFields as $mandatoryField) {
$fieldArray = explode(',', $executionArray[$mandatoryField]);
$finalArray[] = [
$fieldArray[$i]
];
}
}
dd($finalArray);
But it is returning me:
array:6 [
0 => array:1 [
0 => "245"
]
1 => array:1 [
0 => "45"
]
2 => array:1 [
0 => "19"
]
3 => array:1 [
0 => "225"
]
4 => array:1 [
0 => "65"
]
5 => array:1 [
0 => "17"
]
]
The following logic might help you solve your issue:
<?php
$arr = [
"manufacturer" => "BB",
"width" => "245, 225",
"height" => "45, 65",
"diameter" => "19, 17",
"type" => "AA",
];
foreach ($arr as $key => $val) {
$split[$key] = explode(',', $val);
}
$mandatoryFields = [
'width',
'height',
'diameter',
];
// keep only mandatory fields
$split = array_intersect_key($split, array_flip($mandatoryFields));
$items = count($split[$mandatoryFields[0]]); // number of 'items' per mandatory field
for ($i = 0; $i < $items; $i++) {
$result[$i] = array_column($split, $i);
}
echo '<pre>';
print_r($result);
echo '</pre>';
Output:
Array
(
[0] => Array
(
[0] => 245
[1] => 45
[2] => 19
)
[1] => Array
(
[0] => 225
[1] => 65
[2] => 17
)
)
Essentially, this is an "explode & transpose" task. If the required columns are known, then they can be hardcoded like this: (Demo)
var_export(
array_map(
null,
explode(', ', $arr['width']),
explode(', ', $arr['height']),
explode(', ', $arr['diameter'])
)
);
Output:
array (
0 =>
array (
0 => '245',
1 => '45',
2 => '19',
),
1 =>
array (
0 => '225',
1 => '65',
2 => '17',
),
)
If the columns need to be dynamic, then map the required fields, explode on commas and unpack the generated rows to maintain a functional coding style: (Demo)
var_export(
array_map(
null,
...array_map(
fn($f) => explode(', ', $arr[$f]),
$mandatoryFields
)
)
);

Group data sets by specific column where column names are the first row in each set

I have an array of arrays like this:
$data = array (
'data1' => array (
0 =>
array (
0 => 'ID',
1 => 'PinCode',
2 => 'Date',
),
1 =>
array (
0 => '101',
1 => '454075',
2 => '2012-03-03',
),
2 =>
array (
0 => '103',
1 => '786075',
2 => '2012-09-05',
),
),
'data2' => array (
0 =>
array (
0 => 'Balance',
1 => 'ID',
),
1 =>
array (
0 => '4533',
1 => '101',
)
),
'data3' => array (
0 =>
array (
0 => 'Active',
1 => 'ID',
),
1 =>
array (
0 => 'Yes',
1 => '101',
),
2 =>
array (
0 => 'No',
1 => '103',
)
),
);
In the $data array there are three arrays named data1, data2 and data3 respectively.
In each array, the first row is the name of the columns and the remaining rows are the values for those columns (think of it like a table).
In each data1,data2 and data3 the first row contains a column called ID.
I want to group the data from all three arrays based on the matching ID field such that the final output array is like this:
Desired Output:
$output =
array (
'output' =>
array (
0 =>
array (
0 => 'ID',
1 => 'PinCode',
2 => 'Date',
3 => 'Balance',
4 => 'Active',
),
1 =>
array (
0 => '101',
1 => '454075',
2 => '2012-03-03',
3 => '4533',
4 => 'Yes',
),
2 =>
array (
0 => '103',
1 => '786075',
2 => '2012-09-05',
3 => 'null',
4 => 'No',
),
)
);
What I tried(just a attempt to combine data1 and data2) :
$d1=$data['data1'];
$d2=$data['data2'];
if(count($d1)>count($d2))
{
$arr1=array();
$arr2=array();
$arr3=array();
$column1=$d1[0];
$column2=$d2[0];
for($i=1;$i<=(count($d1)-1);$i++)
{
if($i<count($d2))
$arr2[]=array_combine($column2,$d2[$i]);
else
$arr2[]=array_combine($column2,array('0'=>'','1'=>''));
}
for($i=1;$i<=(count($d1)-1);$i++)
{
$arr1[]=array_combine($column1,$d1[$i]);
}
for($i=0;$i<=(count($arr1)-1);$i++)
{
$arr3[]=array_merge($arr1[$i],$arr2[$i]);
}
print_r($arr3);
}
I need help regarding a neat code to combine any number of arrays.
Notice that missing elements should receive a null value.
How do I get the output I have mentioned above?
This splits it into 2 steps, first accumulate all the data by the ID, also all the header columns are collected. Then use this data to create output array with blanks where the data is missing.
Comments in code...
$store = [];
$headers = [];
foreach ( $data as $set ) {
$headerRow = array_shift($set);
// Collect all header columns
$headers = array_merge($headers, $headerRow);
foreach ( $set as $index => $list ){
// Create associative list of data so they can be combined (i.e. ID fields)
$list = array_combine($headerRow, $list);
// Use ID value as key and create if needed
if ( !isset($store[$list["ID"]]) ) {
$store[$list["ID"]] = $list;
}
else {
$store[$list["ID"]] = array_merge($store[$list["ID"]], $list);
}
}
}
$headers = array_unique($headers);
$output = [ 'output' => [$headers]];
// Create template array, so that missing fields will be set to null
$blank = array_fill_keys($headers, null);
foreach ( $store as $dataRow ) {
// Fill in the fields for this ID and then change to numeric keys
$output['output'][] = array_values(array_merge($blank, $dataRow));
}
Having the keys as the first element of array is not good practice - that why keys are for.
I recommend different approach - use array-combine for connect them and use the ID as key:
foreach($data as $v) {
$keys = array_shift($v); // take the keys
foreach($v as &$e) {
$e = array_combine($keys, $e); // combine the keys and the value
// add or append them according the ID
if (!isset($res[$e['ID']])) $res[$e['ID']] = $e;
else $res[$e['ID']] = array_merge($res[$e['ID']], $e);
}
}
Now you can take this - and if you must convert it back to your structure.
Live example: 3v4l
Largely resembling NigelRen's answer, my snippet will group by ID value, apply default null values where appropriate and prepend a row of column names after all other array building is finished.
Code: (Demo)
$result = [];
$uniqueKeys = [];
// group data by ID
foreach ($data as $rows) {
$keyRow = array_shift($rows);
$uniqueKeys += array_combine($keyRow, $keyRow);
foreach ($rows as $row) {
$assoc = array_combine($keyRow, $row);
$result[$assoc['ID']] = ($result[$assoc['ID']] ?? []) + $assoc;
}
}
// apply default null values where missing element and re-index rows
foreach ($result as &$row) {
$row = array_values(
array_replace(
array_fill_keys($uniqueKeys, null),
$row
)
);
}
// prepend row of column names
array_unshift($result, array_values($uniqueKeys));
var_export($result);
Output:
array (
0 =>
array (
0 => 'ID',
1 => 'PinCode',
2 => 'Date',
3 => 'Balance',
4 => 'Active',
),
1 =>
array (
0 => '101',
1 => '454075',
2 => '2012-03-03',
3 => '4533',
4 => 'Yes',
),
2 =>
array (
0 => '103',
1 => '786075',
2 => '2012-09-05',
3 => NULL,
4 => 'No',
),
)

PHP Sum Multiple Array Values

How to sum multidimensional array values then grouping with date as my code.
If any PHP code what should I try please tell me.
Please see the array code:
$array = array (
0 => array(
'date' => '2015-02-06 10:42:39',
'visit' => 1,
'bounce' => 0
),
1 => array(
'date' => '2015-02-06 13:23:21',
'visit' => 1,
'bounce' => 1
),
2 => array(
'date' => '2015-02-07 04:11:42',
'visit' => 1,
'bounce' => 1
),
3 => array(
'date' => '2015-02-08 11:35:28',
'visit' => 1,
'bounce' => 1
),
4 => array(
'date' => '2015-02-08 15:12:09',
'visit' => 1,
'bounce' => 1
),
5 => array(
'date' => '2015-02-09 15:12:09',
'visit' => 1,
'bounce' => 0
),
);
The result I expect must be when I do foreach:
date visit bounce
2015-02-06 2 1
2015-02-07 1 1
2015-02-08 2 2
2015-02-09 1 0
Here is the code what I've tried. But it just return the date count only.
$items = array_column($array, 'date');
$preg = preg_quote('2015-02-06', '~');
$result = preg_grep('~' . $preg . '~', $items);
echo 'Date <br/>' . count($result);
Please help, thank you in advanced.
Solution using array_walk, substr and array_values functions:
$date_keys = [];
array_walk($array, function($v) use(&$date_keys){
$datePart = $v['date'] = substr($v["date"], 0, 10);
if (isset($date_keys[$datePart])) {
$date_keys[$datePart]['visit'] += $v['visit'];
$date_keys[$datePart]['bounce'] += $v['bounce'];
} else {
$date_keys[$datePart] = $v;
}
});
print_r(array_values($date_keys));
The output:
Array
(
[0] => Array
(
[date] => 2015-02-06
[visit] => 2
[bounce] => 1
)
[1] => Array
(
[date] => 2015-02-07
[visit] => 1
[bounce] => 1
)
[2] => Array
(
[date] => 2015-02-08
[visit] => 2
[bounce] => 2
)
[3] => Array
(
[date] => 2015-02-09
[visit] => 1
[bounce] => 0
)
)
You can create a new array:
$newArr = [];
foreach($array as $arr){
$d = (new DateTime($arr['date']))->format('Y-m-d');
$newArr[$d] = [
"visit" => isset($newArr[$d]['visit']) ? $newArr[$d]['visit'] += $arr['visit'] : $arr['visit'],
"bounce" => isset($newArr[$d]['bounce']) ? $newArr[$d]['bounce'] += $arr['bounce'] : $arr['bounce']
];
}
echo "<pre>";
var_dump($newArr);
echo "</pre>";
Above returns a nice formatted array which you can easily read out in the example you posted:
array(4) {
["2015-02-06"]=>
array(2) {
["visit"]=>
int(2)
["bounce"]=>
int(1)
}
["2015-02-07"]=>
array(2) {
["visit"]=>
int(1)
["bounce"]=>
int(1)
}
["2015-02-08"]=>
array(2) {
["visit"]=>
int(2)
["bounce"]=>
int(2)
}
["2015-02-09"]=>
array(2) {
["visit"]=>
int(1)
["bounce"]=>
int(0)
}
}
You could merge your entries like this:
$items = [];
foreach ($array as $value) {
$date = substr($value['date'], 0, 10);
if (!isset($items[$date]) {
$items[$date] = [
'date' => $date,
'visit' => 0,
'bounce' => 0
];
}
$items[$date]['visit'] += $value['visit'];
$items[$date]['bounce'] += $value['bounce'];
}
By using date as a key in the $items array, we make sure we sum up the visit and bounce for each date instead of appending them.
If you'd ever want to get rid of the keys, you can simply use array_values.
If your array is called $myArray, and the new array your creating is $myDateArray:
$myDateArray = array();
foreach ($myArray as $value)
{
list($date_string, $other) = explode(" ", $value['date']);
if (array_key_exists($date_string, $myDateArray))
{
//adding the visits and bounces
$myDateArray[$date_string]['visit'] = $myDateArray[$date_string]['visit'] + $value['visit'];
$myDateArray[$date_string]['bounce'] = $myDateArray[$date_string]['bounce'] + $value['bounce'];
}
else
{
//putting the first values (of the key $date_string) into $myDateArray
array_push($myDateArray, array($date_string, $value['visit], $value['bounce']));
}
}
Let me know if that worked for you!
The code creates a new array which contains the summed result of your array.
$resultArray = [];
foreach($array as $row){
$dateObj = DateTime::createFromFormat('Y-m-d H:i:s', $row['date']);
$key = $dateObj->format('Y-m-d');
if(!array_key_exists($key, $resultArray)){
$resultArray[$key] = ['visit' => $row['visit'], 'bounce' => $row['bounce']];
}
else {
$resultArray[$key]['visit'] += $row['visit'];
$resultArray[$key]['bounce'] += $row['bounce'];
}
}
Result:
array (size=4)
'2015-02-06' =>
array (size=2)
'visit' => int 2
'bounce' => int 1
'2015-02-07' =>
array (size=2)
'visit' => int 1
'bounce' => int 1
'2015-02-08' =>
array (size=2)
'visit' => int 2
'bounce' => int 2
'2015-02-09' =>
array (size=2)
'visit' => int 1
'bounce' => int 0
Does this do the trick ?
<?php
$array = array (
0 => array(
'date' => '2015-02-06 10:42:39',
'visit' => 1,
'bounce' => 0
),
1 => array(
'date' => '2015-02-06 13:23:21',
'visit' => 1,
'bounce' => 1
),
2 => array(
'date' => '2015-02-07 04:11:42',
'visit' => 1,
'bounce' => 1
),
3 => array(
'date' => '2015-02-08 11:35:28',
'visit' => 1,
'bounce' => 1
),
4 => array(
'date' => '2015-02-08 15:12:09',
'visit' => 1,
'bounce' => 1
),
5 => array(
'date' => '2015-02-09 15:12:09',
'visit' => 1,
'bounce' => 0
),
);
$results = [];
foreach($array as $e)
{
$date = explode(' ',$e['date'])[0] ;
if(!isset($results[$date]))
$results[$date] = ['visit'=>0 , 'bounce'=>0] ;
$results[$date]['visit'] += $e['visit'];
$results[$date]['bounce'] += $e['bounce'];
}
print_r($results);
Technique/ Mechnism
$result = array();
$items = array_column($array, 'date');
for($i=0; $i < count($items); $i++){
$date = date("Y-m-d", strtotime($items[$i]));
$result[$date][0] += $array[$i]['visit'];
$result[$date][1] += $array[$i]['bounce'];
}
Result
Please design your output as required.
echo "date visit bounce<br/>";
foreach($result as $key => $value){
echo $key." ".$value[0]." ".$value[1]."<br/>";
}
Output
date visit bounce
2015-02-06 2 1
2015-02-07 1 1
2015-02-08 2 2
2015-02-09 1 0

Multi Array Counting values of sub-arrays

I have multi-dimensional array:
$array = array(
array(
"id" => 1,
"value" => 100
),
array(
"id" => 1,
"value" => 200
),
array(
"id" => 1,
"value" => 300
),
array(
"id" => 2,
"value" => 2
),
array(
"id" => 2,
"value" => 5
),
array(
"id" => 3,
"value" => 10.50
),
);
I need to get new multi-dimensional array, like this:
$newArray = array(
array(
"id" => "1",
"sum_of_values" => 600
),
array(
"id" => 2,
"sum_of_values" => 7
),
array(
"id" => 3,
"sum_of_values" => 10.50
),
);
I mean, key [sum_of_values] should counts values in sub-arrays with the same key [id].
I've tried this:
$cnt = count($array);
$finalArray = array();
$tempArray = array();
for($i = 0; $i < $cnt; $i++){
if($array[$i]["id"] == $array[$i++]["id"]){
$search = $array[$i]["id"];
$column = "value";
$sum = array_sum(
array_map(
function($data) use ($column) {
return $data[$column];
},
array_filter(
$array,
function($data) use ($search) {
return fnmatch($search, $data["id"]);
}
)
)
);
$tempArray = array(
"id" => $array[$i]["id"],
"sum_of_values" => $sum
);
$finalArray[] = $tempArray;
} else {
$tempArray = array(
"id" => $array[$i]["id"],
"sum_of_values" => $array[$i]["value"]
);
}
}
And it works, it counts values of sub-arrays with same [id]. But the problem is not returning sub-arrays, key [id] of which doesn't have same examples. In my case - sub-array with [id] = 3 will not return. Also, sub-arrays with [id] = 1 and [id] = 2 will repeat [n-n/2] times.
I think, that problem is in using cycle - for ($i = 0; $i < $cnt; $i++) and condition if ($array[$i]["id"] == $array[$i++]["id"]), because it looks stupid, but i can't find anything else.
Your function seems too complicated. How about this?
$result = array();
foreach ($array as $item) {
if (!empty($result[$item['id']]))
$result[$item['id']]['sum_of_values'] += $item['value'];
else
$result[$item['id']] = array(
'id' => $item['id'],
'sum_of_values' => $item['value']
);
}
If you print_r($result) you get:
Array
(
[1] => Array
(
[id] => 1
[sum_of_values] => 600
)
[2] => Array
(
[id] => 2
[sum_of_values] => 7
)
[3] => Array
(
[id] => 3
[sum_of_values] => 10.5
)
)
Just as an alternative: this is a sitter for array_reduce():
$totals = array_reduce($array, function($reduction, $current){
$id = $current["id"];
if (empty($reduction[$id])){
$reduction[$id] = ["id"=>$id, "sum_of_values"=>0];
}
$reduction[$id]["sum_of_values"] += $current["value"];
return $reduction;
}, []);
I prefer this sort of approach to generic looping/processing: I think it's a bit cleaner. But mileage varies, obviously.

array grouping which contains same fields

I have an array which contains following values.
array(
'dates' => array(
(int) 0 => '2013-04-22',
(int) 1 => '2013-04-23',
),
'publisherName' => array(
(int) 0 => 'Comp1',
(int) 1 => 'Comp2',
),
'loaded' => array(
(int) 0 => (int) 2189,
(int) 1 => (int) 37,
),
'clicks' => array(
(int) 0 => (int) 0,
(int) 1 => (int) 0,
),
'ctr' => array(
(int) 0 => (int) 0,
(int) 1 => (int) 0,
)
)
What I want to produce is getting company based data on different dates like the array below.
How am I able to create an array which is like;
array (
'2013-04-22'=>array(
'publisherName'=>'Comp1',
'loaded'=>2189,
'clicks'=>0,
'ctr'=>0),
'2013-04-23'=>array(
'publisherName'=>'Comp2',
'loaded'=>37,
'clicks'=>0,
'ctr'=>0)
...
)
Which contains daily stats but which comes from publishername field.
Any ideas?
Here's an example that doesn't rely on any keys, the only requirement is that date is the first array in your original array:
// $array is your original array
$dates = array_shift($array);
$output = array();
foreach ($array as $k => $a) {
foreach ($a as $i => $v) {
$output[$i][$k] = $v;
}
}
$output = array_combine($dates, $output);
Let the initial array be $a and the desired array $b;
Code:
$b = array();
$count = 0;
foreach($a['dates'] as $date) {
$b[$date] = array(
'name' => $a['publisherName'][$count],
'loaded'=> $a['loaded'][$count],
'clicks'=> $a['clicks'][$count],
'ctr'=> $a['ctr'][$count]
);
$count++;
}
Result:
Array
(
[2013-04-22] => Array
(
[name] => Comp1
[loaded] => 2189
[clicks] => 0
[ctr] => 0
)
[2013-04-23] => Array
(
[name] => Comp2
[loaded] => 37
[clicks] => 0
[ctr] => 0
)
)
<?php
$data = $yourarray;
$new = array();
foreach($data['dates'] as $key=>$value) {
$new[$value] = array('name'=>$data['publisherName'][$key],'loaded'=>$data['loaded'][$key],'clicks'=>$data['clicks'][$key],'ctr'=>$data['ctr'][$key]);
}
echo '<pre>';
print_r($new);
?>
$newArr = array();
foreach($data['dates'] as $key=>$value) {
$new[$value] = array('name'=>$data['publisherName'][$key],'loaded'=>$data['loaded'][$key],'clicks'=>$data['clicks'][$key],'ctr'=>$data['ctr'][$key]);
}
echo '<pre>';
print_r($newArr);
$new_arr = your array;
$finalArray = array();
foreach($new_arr['dates'] as $key => $value){
$finalArray[$value] = array(
'publisherName'=>$new_arr['publisherName'][$key],
'loaded'=>$new_arr['loaded'][$key],
'clicks'=>$new_arr['clicks'][$key],
'ctr'=>$new_arr['ctr'][$key]
);
}
Output :
Array
(
[2013-04-22] => Array
(
[publisherName] => Comp1
[loaded] => 2189
[clicks] => 0
[ctr] => 0
)
[2013-04-23] => Array
(
[publisherName] => Comp2
[loaded] => 37
[clicks] => 0
[ctr] => 0
)
)

Categories