Merge and sum multiple multidimensional associative arrays PHP - php

I have an array
$data = array(
1 => array(1 => 11 ,2 => 21, 3 => 31),
2 => array(1 => 21 ,2 => 22),
3 => array(1 => 31 ,2 => 23, 3 => 32),
);
which I want to transform to
$data = array(
1 => array(1 => 11 ,2 => 21, 3 => 31, 'Total' => 63),
2 => array(1 => 21 ,2 => 22, 'Total' => 43),
3 => array(1 => 31 ,2 => 23, 3 => 32, 'Total' => 83),
'Total' => array(1 => 63,2 => 46,3 => 63, 'Total' => 172),
);
the keys 1,2,3 are dynamic these could be 1 to 10 and also for sub array. Can you help ?

Most of the answers in Stack Overflow that I could find are related to either sum the elements within an array or the sum of different keys within a multi-dimensional array, but I couldn't find any that addressed both in the same code, so I thought it might be useful to share.
Premises
Your desired result seems incorrect, by manually doing the calculations the totals should be different than what you described. So I am assuming you just got your example wrong and this is the actual result you want:
$data = array(
1 => array(1 => 11 ,2 => 21, 3 => 31, 'Total' => 63),
2 => array(1 => 21 ,2 => 22, 'Total' => 43),
3 => array(1 => 31 ,2 => 23, 3 => 32, 'Total' => 86),
'Total' => array(1 => 63,2 => 66,3 => 63, 'Total' => 192),
);
Loop and sum arrays
So you basically want two things:
For each of the first-level elements of your arrays, you want to calculate the Total. This is done with array_sum and you can apply it by doing a foreach on the first level of your $data array. More info on array_sum and the official php documentation.
Then for each of the second-level elements, you want to sum the ones with the same key. To do so, you can use a simple foreach that keeps track of the second-level key and sums each value
Resulting Code
The resulting code combines these two points with the following logic:
Initialize the Totals key in the first-level of your $data array
Loop the first-level elements
For each first-level element, use array_sum to get the Total
Loop the second-level elements
For each Index of the second level (1, 2, 3), initialize the same index in the Totals array
For each Index of the second level (1, 2, 3), sum the counter (11, 21, 31) in the Totals array
I've added comments to make it clearer:
// Initialize the 'Total' key in the first-level of the array. This will be populated in the foreach
$data['Total'] = [];
// Loop the first level of the array
foreach ($data as $key => $values) {
// Calculate the sum of the first level of the array, using array_sum
$data[$key]['Total'] = array_sum($data[$key]);
// Loop the second level of the array
foreach ($values as $index => $count) {
// For each INDEX, create the corresponding element of the Totals array
if (!isset($data['Total'][$index])) {
$data['Total'][$index] = 0;
}
// Sum this element $count in the Total array
$data['Total'][$index] += $count;
}
}

Related

Evenly push values from a flat array into same positioned rows of a 2d array [duplicate]

This question already has answers here:
Push elements from one array into rows of another array (one element per row)
(4 answers)
Closed 5 months ago.
I need to evenly/synchronously push values from my second array into the rows of my first array.
The arrays which have the same size, but with different keys and depths. The first is an array of rows and the second is a flat array.
$array1 = [
12 => [130, 28, 1],
19 => [52, 2, 3],
34 => [85, 10, 5]
]
$array2 = [4, 38, 33]
Preferred result:
[
12 => [130, 28, 1, 4],
19 => [52, 2, 3, 38],
34 => [85, 10, 5, 33]
]
(I would like to keep the same indices of array 1, however it is not mandatory.)
I have tried these methods, but none of them work because the first array keys are unpredictable.
$final = [];
foreach ($array1 as $idx => $val) {
$final = [$val, $array2[$idx]];
}
Another:
foreach ($array1 as $index => $subArray) {
$array1 [$index][] = $array2[$index];
}
Here is one way to do this:
$merged = array_map('array_merge', $array1, array_chunk($array2, 1));
$result = array_combine(array_keys($array1), $merged);
The second step with array_combine is necessary to reapply the non-sequential keys because array_map won't preserve them in the first step.
An example using foreach
<?php
$a = [
2 => [130, 28, 1, 1, 6],
3 => [52, 2, 3, 3, 27]
];
$b = [5, 38];
$output = [];
$idx = 0;
foreach ($a as $key => $value) {
$value[] = $b[$idx];
$output[$key] = $value;
++$idx;
}
print_r($output);
Sandbox HERE
You can loop $array1 using a foreach to get the current key $index
Get the value from $array2 by using a counter as the array key which, is incremented by 1 for every iteration.
Then add the value to the end of the current array.
$array1 = [
2 => [130, 28, 1, 1, 6],
3 => [52, 2, 3, 3, 27],
13 => [41, 20, 27, 13, 37]
];
$array2 = [89, 99, 109];
$counter = 0;
foreach ($array1 as $index => $subArray) {
$array1[$index][] = $array2[$counter++];
}
print_r($array1);
Output
Array
(
[2] => Array
(
[0] => 130
[1] => 28
[2] => 1
[3] => 1
[4] => 6
[5] => 89
)
[3] => Array
(
[0] => 52
[1] => 2
[2] => 3
[3] => 3
[4] => 27
[5] => 99
)
[13] => Array
(
[0] => 41
[1] => 20
[2] => 27
[3] => 13
[4] => 37
[5] => 109
)
)
See a PHP demo.
Maintaining a counter while iterating is a simple way of accessing second array values while iterating the first. It is not necessary to make multiple passes of the arrays, just one iteration is all that is required.
Codes: (Demos)
a mapper:
$i = -1;
var_export(
array_map(fn($row) => array_merge($row, [$array2[++$i]]), $array1)
);
a looper:
$i = -1;
foreach ($array1 as &$row) {
array_push($row, $array2[++$i]);
}
var_export($array1);
a walker:
$i = -1;
array_walk($array1, fn(&$row, $k) => array_push($row, $array2[++$i]));
var_export($array1);
If you don't care about preserving the first array's keys in the result array, then you can simply use:
var_export(
array_map('array_merge', $array1, array_chunk($array2, 1))
);

PHP - array_map 2 columns and remove duplicate rows by key [duplicate]

This question already has answers here:
Filter/Remove rows where column value is found more than once in a multidimensional array
(4 answers)
Closed 9 months ago.
I have the following array dynamically generated from an external API:
$purchases = array(
array('product_id' => 7, 'receipt' => R13D13),
array('product_id' => 5, 'receipt' => Y28Z14),
array('product_id' => 7, 'receipt' => R02310),
array('product_id' => 5, 'receipt' => E11403)
);
Desired output:
$purchases_new = array(
array('product_id' => 7, 'receipt' => R13D13),
array('product_id' => 5, 'receipt' => Y28Z14)
);
Due to the nature of the API I have only been able to pull data using array_map:
$purchases_unique = array_unique(
array_map(function($pid){ return $pid['product_id']; }, $purchases)
);
print_r($purchases_unique);
Outputs
array((array[251] => 7) array([252] => 5))
Output completely not what I want :( How to array_map 2 columns product_id' and receipt and remove duplicate rows based on product_id?
I want to achieve the following:
array_map both columns? (product_id and receipt)
Remove product_id duplicates rows based on product_id key?
If you want the first occurrence use #TheFirstBird answer. If you want the last occurrence you can do it simpler buy using array_column and array_values as:
$newArr = array_values(array_column($purchases, null, "product_id"));
Notice the second argument of array_column is null meaning take the entire element.
Live example: 3v4l
References: array-column, array-values
I think this will do what you're looking for:
Just adds the unique product_ids (with receipt) to a new array:
$purchases_new = array_reduce($purchases, function($acc, $p) {
if(!in_array($p['product_id'], array_column($acc, 'product_id'))) {
$acc[] = $p;
}
return $acc;
}, []);
Output:
array (
0 =>
array (
'product_id' => 7,
'receipt' => 'R13D13',
),
1 =>
array (
'product_id' => 5,
'receipt' => 'Y28Z14',
),
)
Try it online!
As per the comment It does not matter if it's the first or last occurrence, you could use a foreach and create a $result array for the non duplicates.
For every iteration, use the product_id as the array key and check if it already has been set.
If it is not, then add it.
$purchases = array(
array('product_id' => 7, 'receipt' => "R13D13"),
array('product_id' => 5, 'receipt' => "Y28Z14"),
array('product_id' => 7, 'receipt' => "R02310"),
array('product_id' => 5, 'receipt' => "E11403")
);
$result = [];
foreach($purchases as $key => $purchase) {
if (!isset($result[$purchase["product_id"]])) {
$result[$purchase["product_id"]] = $purchase;
}
}
print_r($result);
Result
Array
(
[7] => Array
(
[product_id] => 7
[receipt] => R13D13
)
[5] => Array
(
[product_id] => 5
[receipt] => Y28Z14
)
)
Php demo

Combine (merge) 2 arrays by keys and change keys name in the result array

I have these two arrays as output:
Value Array
(
[0] => 10100153
[1] => 2007
[2] => 350
[3] => 804082
[4] => WW006
[5] => WHT/NNY/OXGM
[6] => 35/38
[7] => 804082 WW00635/38
[8] => 0,00138857
[9] => Champion 3pk Quarter Socks
)
Numbers Array
(
[0] => 1
[1] => 2
[2] => 3
[3] => 4
[4] => 5
[5] => 6
[6] => 7
[7] => 8
[8] => 9
[9] => 10
)
I want to combine them and change the key value of the value array in value and the numbers array in numbers, so it looks something like this:
Desire output
['Value' => '10100153', 'Number' => 1],
['Value' => '2007', 'Number' => 2],
['Value' => '390', 'Number' => 3],
['Value' => '804715', 'Number' => 4],
['Value' => 'WW001', 'Number' => 5],
['Value' => 'WHT/WHT/WHT', 'Number' => 6],
['Value' => '39/42', 'Number' => 7],
['Value' => '804715 WW00139/42', 'Number' => 8],
['Value' => '0.00138857', 'Number' => 9],
['Value' => '3pk Quarter Socks', 'Number' => 10]
All I can find is array_combine and array_merge, but array_merge just adds the numbers array to the end of the value array, and array_combine adds the numbers to the end of the text of the value array
You can use array_map (doc) and array_combine (doc) as:
$res = array_map(null, $valuesArray, $numbersArray);
$keys = array("Value", "Number");
$res = array_map(function ($e) use ($keys) {return array_combine($keys, $e);}, $res);
Notice the use of null in array_map. From documentation:
An interesting use of this function is to construct an array of arrays, which can be easily performed by using NULL as the name of the callback function
This way you can merge more arrays - just remember to add the correct key to $keys
Live example: 3v4l
You could use a regular foreach loop to iterate over your values array. At each element in the values array you can get its corresponding element in the numbers array by using the current index.
At each iteration (each loop of your values array) you can add an associative array into a resulting array (here I called it $res).
See example below:
$values = ["10100153", "2007", "350", "804082", "WW006", "WHT/NNY/OXGM", "35/38", "804082 WW00635/38", "0,00138857", "Champion 3pk Quarter Socks"];
$nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
$res = []; // create empty array to hold associative arrays
foreach($values as $i=>$val) { // loop over your values array, where the index is $i and the value is $val
$num = $nums[$i]; // get the number at the given index
$res[$i] = ["Value" => $val, "Number" => $num]; // set the index in the resulting array to hold a newly formed associative array
}
print_r($res); // print the results
You can skip the extra round of iterating (while transposing with array_map(null...)) as demonstrated in dWinder's answer by passing both input arrays into array_map() and using the splat operator to receive the arguments inside the function. My snippet will have half of the computational complexity of dWinder's solution -- just one loop instead of two.
Code: (Demo) (or with compact())
$values = ["10100153", "2007", "350", "804082", "WW006", "WHT/NNY/OXGM", "35/38", "804082 WW00635/38", "0,00138857", "Champion 3pk Quarter Socks"];
$numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
$keys = ["Value", "Number"];
var_export(
array_map(function(...$data) use ($keys) {
return array_combine($keys, $data);
}, $values, $numbers)
);
Output:
array (
0 =>
array (
'Value' => '10100153',
'Number' => 1,
),
1 =>
array (
'Value' => '2007',
'Number' => 2,
),
2 =>
array (
'Value' => '350',
'Number' => 3,
),
...you get the point
)

PHP associative array with non-unique keys

Struggling with concept of an associative array that maps userIDs to partIDs and re-order quantity for the part.
We have bunch of parts that I need to re-order, but I must keep track of which user needs what parts re-purchased for them. The list of UserIDs comes from one table, then the inventory_used comes from another table.
Suppose a list like this:
Example One:
UserID PartID Qty_Used
1 3 2
1 4 7
2 1 4
2 4 3
3 3 5
After creating an array with the above information, I must create a re-order form (table) for the parts. Therefore, ignoring the userID, group them by the partID, and sum up the total Qty (per part). The re-order table should look something like this:
Example Two:
PartID Qty_to_Reorder
1 4
3 7
4 10
I know I'm going to take a ton of downvotes for failing to show code, but I can't wrap my mind around this seemingly simple problem. (This is for my office, not a school project).
How do I:
(1) Structure the first array (what would the loop to create it look like?), and then
(2) Loop through that array to summarize/group partIDs => Qty for re-order report, as per 2nd example above?
For the first loop, i was thinking of something like this:
Loop through UserIDs {
Loop through PartIDs {
$arrReorder[UserID][PartID] = Qty_Used;
}
}
Is that correct? How would I loop through $arrReorder to sum-up the qty used for each partID, and get the re-order report (example 2)?
SELECT SUM(Qty_Used) AS total FROM mytable WHERE PartID=3
PS: Using PHP
<?php
$data = array();
$data[] = array("UserID" => 1, "PartID" => 3, "Qty_Used" => 2);
$data[] = array("UserID" => 1, "PartID" => 4, "Qty_Used" => 7);
$data[] = array("UserID" => 2, "PartID" => 1, "Qty_Used" => 4);
$data[] = array("UserID" => 2, "PartID" => 4, "Qty_Used" => 3);
$data[] = array("UserID" => 3, "PartID" => 3, "Qty_Used" => 5);
$PartID = 3;
$sum = 0;
foreach ($data as $arr) {
if ($arr['PartID'] == $PartID)
$sum += $arr['Qty_Used'];
}
echo $PartID."\t".$sum."\r\n";
?>
Arrays have a key and value where the value can be another array. Determine what is the key value.
I am assuming you have users consuming parts and you have to re-order the parts from your supplier. No where the user is a customer and the user has a auto re-order policy.
What triggers a reorder? If re-order quantity is 10 and user uses 1, there should be nine in stock.
Create the partsUsed array elements, This is a bit tricky:
$partsUsed[$part][] = array($qtyUsed,$user);
The reason the empty brackets [] is there is to allow duplicate part numbers in the parts used, and still key part as the key.
The value is an array to key the association between user and parts.
what you end up with is a sequentially numbered secondary key where the value is just a throw away, but allows duplicate part numbers.
$partsUsed[3][] = array(2,1);
$partsUsed[4][] = array(7,1);
$partsUsed[1][] = array(4,2);
$partsUsed[4][] = array(3,2);
$partsUsed[3][] = array(5,5);
ksort($partsUsed); // sorts key on part number
var_export($partsUsed);
Result array (var_export):
array (
1 =>
array (
0 =>
array (
0 => 4,
1 => 2,
),
),
3 =>
array (
0 =>
array (
0 => 2,
1 => 1,
),
1 =>
array (
0 => 5,
1 => 5,
),
),
4 =>
array (
0 =>
array (
0 => 7,
1 => 1,
),
1 =>
array (
0 => 3,
1 => 2,
),
),
)
Notice part 3 and 4 have two arrays.
$reorder[1] = 4 ;
$reorder[2] = 7 ;
$reorder[4] = 10 ;
var_export($reorder);
Result array:
array (
1 => 4,
2 => 7,
4 => 10,
)
Now not sure how to determine what gets reordered and how many.
I'll show how to get the values:
foreach($partsUsed as $part => $value){
foreach($value as $k => $v){
echo "Part $part: Qty:$v[0] User:$v[1]\n";
$qtyUsed[$part] += $v[1];
}
}
var_export($qtyUsed);
Outputs:
Part 1: Qty:4 User:2
Part 3: Qty:2 User:1
Part 3: Qty:5 User:3
Part 4: Qty:7 User:1
Part 4: Qty:3 User:2
$qtyUsed
array (
1 => 2,
3 => 4,
4 => 3,
)
foreach ($qtyUsed as $part => $qty){
$order[$part] = $reorder[$part];
}
var_export($order);
Result $order:
array (
1 => 4,
3 => 7,
4 => 10,
)

Subtract row values in Array2 from specific row values in Array1

I would like to subtract the quantity of $array2 from the stocks of $array1.
$array1= ([product_id]=>4, [stocks]=>20)
$array2= ([product_id]=>4, [quantity]=>3)
So that would be:
$array1= ([0]=> 4, [1] => 20);
$array2= ([0]=> 4, [1] => 3);
And then the output should be:
$array1= ([0]=> 4, [1] => 17);
Your array structure looks slightly different with multiple records, the code works out like this in an ugly manner. I'm assuming you're talking about something like this:
$array1 = array(
0=>array('product_id'=>4, 'stocks'=>20),
1=>array('product_id'=>5, 'stocks'=>60));
$array2 = array(
0=>array('product_id'=>4, 'quantity'=>3)
1=>array('product_id'=>5, 'quantity'=>30));
...It's a multi-dimensional array (typical for records pulled from a database).
foreach($array1 as $key=>$value){
foreach($array2 as $key2=>$value2) {
if($value['product_id']==$value2['product_id']){
$value['stocks'] -= $value2['quantity'];
//optimization to avoid searching this again.
unset($array2[$key]);
}
}}
With what you have given the following will do what you are asking for:
if($array1['product_id'] == $array2['product_id']) {
$array1['stocks'] -= $array2['quantity'];
}
If you need to loop through a bigger array then what I have given is only part of the larger puzzle.
Jesse's answer wasn't tested and will not provide the desired output because the "stocks" array wasn't being modified -- a copy of the array was being modified in the loop -- so if you try to print the result to screen, there would be no change.
To modify by reference, use & just before the value variable in the first loop.
Also the unset() key must come from the inner loop to be accurate.
Additionally, if the "sales" "product_id"s are unique, then breaking the inner loop upon matching will improve performance. (This is how array_search() works.)
Code: (Demo)
$stocks = [
['product_id'=>2, 'stocks'=>50],
['product_id'=>3, 'stocks'=>100],
['product_id'=>4, 'stocks'=>20],
['product_id'=>5, 'stocks'=>60]
];
$sales = [
['product_id'=>4, 'quantity'=>3],
['product_id'=>5, 'quantity'=>30]
];
foreach ($stocks as &$row) { // modify by reference
foreach ($sales as $k => $row2) { // search for product_id match
if ($row['product_id'] == $row2['product_id']) {
$row['stocks'] -= $row2['quantity']; // subtract
unset($sales[$k]); // eliminate match from lookup array
break; // assuming $sales['product_id'] values are unique
}
}
}
var_export($stocks);
Output:
array (
0 =>
array (
'product_id' => 2,
'stocks' => 50,
),
1 =>
array (
'product_id' => 3,
'stocks' => 100,
),
2 =>
array (
'product_id' => 4,
'stocks' => 17,
),
3 =>
array (
'product_id' => 5,
'stocks' => 30,
),
)
Alternatively, you can converted the sales array into a flattened, product_id-keyed array to serve as a lookup.
Code: (Demo)
$keyed = array_column($sales, 'quantity', 'product_id');
var_export($keyed);
echo "\n---\n";
foreach ($stocks as &$row) { // modify by reference
if (isset($keyed[$row['product_id']])) { // search for product_id match
$row['stocks'] -= $keyed[$row['product_id']]; // subtract
}
}
var_export($stocks);
Output:
array (
4 => 3,
5 => 30,
)
---
array (
0 =>
array (
'product_id' => 2,
'stocks' => 50,
),
1 =>
array (
'product_id' => 3,
'stocks' => 100,
),
2 =>
array (
'product_id' => 4,
'stocks' => 17,
),
3 =>
array (
'product_id' => 5,
'stocks' => 30,
),
)

Categories