PHP multidimensional array: group repeated values and create "sub array" - php

I have a array with repeated values like this:
[
[
'id' => 112,
'name' => 'John',
'country' => 'Spain',
'age' => 24,
'company' => 'One',
'price' => 10
],
[
'id' => 112,
'name' => 'John',
'country' => 'Spain',
'age' => 24,
'company' => 'Two',
'price' => 15
],
[
'id' => 112,
'name' => 'John',
'country' => 'Spain',
'age' => 24,
'company' => 'Three',
'price' => 20
],
[
'id' => 224,
'name' => 'Paul',
'country' => 'France',
'age' => 25,
'company' => 'One',
'price' => 25
],
[
'id' => 224,
'name' => 'Paul',
'country' => 'France',
'age' => 25,
'company' => 'Two',
'price' => 40
]
]
I need to group same id with name, country and age, and make a "sub array" companies with company and price
array (
112 =>
array (
'id' => 112,
'name' => 'John',
'country' => 'Spain',
'age' => 24,
'companies' =>
array (
0 =>
array (
'company' => 'One',
'price' => 10,
),
1 =>
array (
'company' => 'Two',
'price' => 15,
),
2 =>
array (
'company' => 'Three',
'price' => 20,
),
),
),
224 =>
array (
'id' => 224,
'name' => 'Paul',
'country' => 'France',
'age' => 25,
'companies' =>
array (
0 =>
array (
'company' => 'One',
'price' => 25,
),
1 =>
array (
'company' => 'Two',
'price' => 40,
),
),
),
)
I have tried this code without good results:
$new_array = array();
foreach ($my_array as $item) {
$new_array[$item['id']][] = $item;
$new_array[$item['id']]['companies'][] = $item;
}
Then I tried this:
$new_array = array();
foreach ($my_array as $item) {
$new_array[$item['id']]['temp'] = $item;
$new_array[$item['id']]['companies'][] = $item;
}
Works fine, but I get temp key that I don't want it. For example, with this code I need to access to id items with $new_array [$key]['temp']['id']
I can remove the temp key with another loop:
$final_array = array();
foreach ($new_array $key=>$item) {
$final_array [$key] = $item['temp'];
$final_array [$key]['temp'] = $item['companies'];
}
With this code I can access correctly to id with: $final_array[$key]['id']
Another option is this code:
foreach($array as $v) {
$result[$v['id']]['id'] = $v['id'];
$result[$v['id']]['name'] = $v['name'];
$result[$v['id']]['country'] = $v['country'];
$result[$v['id']]['age'] = $v['age'];
$result[$v['id']]['companies'][] = array('company' => $v['company'],
'price' => $v['price']);
}
But it is not very elegant if we had more keys (phone, email...)
Any ideas?

Nothing wrong with the code you've provided at the end, but here's some ideas for how to make it more tolerant to more array values (as you mentioned, phone, email, etc).
This uses the handy PHP array_diff_key function to remove the array elements you don't want from the "core" records. Then, applying array_diff_key to get those same array elements INTO the "company" records.
// set up the array of keys you don't want in the "original" record
$exclude = ['company' => FALSE, 'price' => FALSE];
// Your original loop
foreach($array as $v) {
// not strictly necessary, but helps readability
$id = $v['id'];
// Assign the "core" record all the values you want, excluding those defined above
// in this case, will remove "company" and "price"
$record = array_diff_key( $v, $exclude );
// only set this if not already set, otherwise wipes out previous companies
if ( ! isset($result[$id] ) ) {
$result[$id] = $record;
$result[$id]['companies'] = [];
}
// strip off the OTHER values from the array you don't want stored with the company
// this will automatically pick up the field NOT excluded above
// in this case, will remove all BUT "company" and "price"
$company = array_diff_key( $v, $record );
$result[$id]['companies'][] = $company;
}

One way to rome...
$collector=array();
foreach($array as $set){
//get a copy
$ref = $set;
//unset company data
unset($ref['company'],$ref['price']);
//make a refer
$r=$ref['id'];
//setup main data if not set before
if(!isset($collector[$r])){
$collector[$r]=$ref;
}
//collect company data
$collector[$r]['companies'][] = array('company'=>$set['company'],'price'=>$set['price']);
}
print_r($collector);

Similar to other answers:
$info = array (
array('id' => 112, 'name' => 'John', 'country' => 'Spain', 'age' => 24, 'company' => 'One', 'price' => 10),
array('id' => 112, 'name' => 'John', 'country' => 'Spain', 'age' => 24, 'company' => 'Two', 'price' => 15),
array('id' => 112, 'name' => 'John', 'country' => 'Spain', 'age' => 24, 'company' => 'Three', 'price' => 20),
array('id' => 224, 'name' => 'Paul', 'country' => 'France', 'age' => 25, 'company' => 'One', 'price' => 25),
array('id' => 224, 'name' => 'Paul', 'country' => 'France', 'age' => 25, 'company' => 'Two', 'price' => 40)
);
$grouped = array();
foreach ($info as $item) {
if (!isset($grouped[$item['id']])) {
$grouped[$item['id']] = $item;
}
$grouped[$item['id']]['companies'][] = array($item['company'],$item['price']);
unset($grouped[$item['id']]['company']);
unset($grouped[$item['id']]['price']);
}
print_r($grouped);

The sample data does not seem to require grouping by more than the id column.
The snippet below will extract/consume the last two elements in each row before pushing the first encountered row with a unique id, then it unconditionally pushes those two extracted rows into the group's companies subset.
Code: (Demo)
$result = [];
foreach ($array as $row) {
$companies = array_splice($row, -2);
$result[$row['id']] ??= $row;
$result[$row['id']]['companies'][] = $companies;
}
var_export($result);

Related

Create Array From Key

Thank you in advance. Is there any way to create a multidimensional array from key names.
$array = array(
'brand/name' => 'BRAND_NAME',
'brand/model' => 'MODEL_NO',
'brand/inv/qty' => '20',
'brand/inv/cost' => '30',
'wh' => 'NY',
'brand/inv/sales' => '40'
);
Transform to this array.
$array = array(
'brand' => array(
'name' => 'BRAND_NAME',
'model' => 'MODEL_NO',
'inv' => array(
'qty' => 20,
'cost' => 30,
'sales' => 40,
)
),
'wh' => 'NY'
);
Thank you !
Try my code (I used the reference operator "&" to get the successive inner arrays):
Input array:
$array = array(
'brand/name' => 'BRAND_NAME',
'brand/model' => 'MODEL_NO',
'brand/inv/qty' => '20',
'brand/inv/cost' => '30',
'wh' => 'NY',
'brand/inv/sales' => '40'
);
php code:
<?php
$resultArray = array();
foreach($array as $path => $element) {
$pathArray = explode("/", $path);
$auxRef = &$resultArray;
foreach($pathArray as $pathPart) {
if(! array_key_exists($pathPart, $auxRef)) {
$auxRef[$pathPart] = array();
}
$auxRef = &$auxRef[$pathPart];
}
$auxRef = $element;
unset($auxRef);
}
?>
Result array:
array ( 'brand' => array ( 'name' => 'BRAND_NAME', 'model' => 'MODEL_NO', 'inv' => array ( 'qty' => '20', 'cost' => '30', 'sales' => '40', ), ), 'wh' => 'NY', )

Merge php array with dynamic key

If I query out the array like this form:
$arr1:
0 =>
'id' => 1,
'name' => 'a'
1 =>
'id' => 2,
'name' => 'b'
2 =>
'id' => 3,
'name' => 'c'
3 =>
'id' => 4,
'name' => 'd'
$arr2:
0 =>
'id' => 1,
'parent' => '1a'
1 =>
'id' => 2,
'parent' => '2b'
2 =>
'id' => 3,
'parent' => '3c'
3 =>
'id' => 4,
'parent' => '4d'
When I need to merge these two I can done it using foreach loop.
The problem comes to when the $arr1 is dynamic data(need to use for pagination), i can't merge well using array_merge due to $arr2 is fixed data.
example:
first time:
$arr1:
0 =>
'id' => 1,
'name' => 'a'
1 =>
'id' => 2,
'name' => 'b'
$arr2:
0 =>
'id' => 1,
'parent' => '1a'
1 =>
'id' => 2,
'parent' => '2b'
2 =>
'id' => 3,
'parent' => '3c'
3 =>
'id' => 4,
'parent' => '4d'
second time:
$arr1:
0 =>
'id' => 3,
'name' => 'c'
1 =>
'id' => 4,
'name' => 'd'
$arr2:
0 =>
'id' => 1,
'parent' => '1a'
1 =>
'id' => 2,
'parent' => '2b'
2 =>
'id' => 3,
'parent' => '3c'
3 =>
'id' => 4,
'parent' => '4d'
I had try using foreach loop
foreach($arr1 as $k => $value){
$group[$k]['parent'] = $arr2[$k]['parent'];
$group[$k]['name'] = $value['name'];
$group[$k]['id'] = $value['id'];
}
It comes to the parent value does not change as $key is fixed.
The expected output should all the element in 1 array:
0 =>
'id' => 1,
'parent' => '1a'
'name'='a'
1 =>
'id' => 2,
'parent' => '2b'
'name'='b'
I think this is what you need...
The easiest way to do this is to re-index the second array with the ID as the key. I use array_column() to do this. Then use the id from $arr1 to access the parent value from the newly indexed array...
$parent = array_column($arr2, 'parent', 'id');
$group = [];
foreach($arr1 as $k => $value){
$group[$k]['parent'] = $parent[$value['id']];
$group[$k]['name'] = $value['name'];
$group[$k]['id'] = $value['id'];
}
In case $arr2 has more information, just use null as the second parameter to array_column(). Then you need to add the column name when you access that array...
$parent = array_column($arr2, null, 'id');
$group = [];
foreach($arr1 as $k => $value){
$group[$k]['parent'] = $parent[$value['id']]['parent'];
$group[$k]['name'] = $value['name'];
$group[$k]['id'] = $value['id'];
}
Try this:
foreach($arr1 as $k=>$v) {
$arr1[$k]['parent'] = $arr2[$k]['parent'];
}
print_r( $arr1 );

Transform keys and values in an associative array

I have 2 associative Arrays but they are in the wrong form. The first
Array(
'name' => 'adam',
'age' => '13'
)
and the second one shoulb be combined (merged?)
Array(
'key' => 'pet',
'value' => 'dog'
)
that the result would be like
Array(
'name' => 'adam',
'age' => '13',
'pet' => 'dog'
)
Can anyone give me a hint/solution?
EDIT: I did it that way:
$result = array_merge($item, array_column($metas, 'v', 'k'));
Thanks
$data1 = Array(
'name' => 'adam',
'age' => '13'
);
$data2 = Array(
'key' => 'pet',
'value' => 'dog'
);
let $result=[];
foreach ($data1 as $key => $value) {
$result[$data2['key']]=$data2['value'];
$result=$data1+$result;
}
print_r($result);

PHP finding same record in array

I would like to detect same records and then change quantity of the one record and delete the others. For example, given the following array:
'Cart' => [
(int) 0 => [
'size' => '38',
'code' => '5',
'qn' => (int) 1
],
(int) 1 => [
'size' => '37',
'code' => '5',
'qn' => (int) 1
],
(int) 2 => [
'size' => '37',
'code' => '5',
'qn' => (int) 1
]
],
i would like to print:
'Cart' => [
(int) 0 => [
'size' => '38',
'code' => '5',
'qn' => (int) 1
],
(int) 1 => [
'size' => '37',
'code' => '5',
'qn' => (int) 2
]
],
It looks to me that you're trying to sum quantities (qn) on duplicate sizes (size) and codes (code). One way to achieve this is by looping through the array containing the cart items and building out a new array. I suggest reading about PHP arrays and array_key_exists to learn more as they're used below:
<?php
$arr = [
['size' => '38', 'code' => 5, 'qn' => 1],
['size' => '37', 'code' => 5, 'qn' => 1],
['size' => '37', 'code' => 5, 'qn' => 1],
['size' => '37', 'code' => 4, 'qn' => 1],
];
$newArr = [];
foreach ($arr as $value) {
$key = $value['size'] . ':' . $value['code'];
if (array_key_exists($key, $newArr)) {
$newArr[$key]['qn'] += $value['qn'];
continue;
}
$newArr[$key] = $value;
}
// Resets keys
$newArr = array_values($newArr);
print_r($newArr);
I dont know it is enough for your problem, but try function array_unique
Iterate your array and use array_diff on each subarray.
If the returned array doesn't contain neither size nor code, add the two qn
The function array_unique() works for single dimension array. To find unique elements from multi-dimension array, we need to do a little modification.
Try this:
$array = array_map("serialize", $array);
$output = array_map("unserialize", array_unique($array));
If you want to follow another approach, use this as a reference: Create multidimensional array unique
Please go through following code.
$result = [
0=> ['size' => '38',
'code' => '5',
'qn' => 1
],
1=> ['size' => '37',
'code' => '5',
'qn' => 1
],
2=> ['size' => '37',
'code' => '5',
'qn' => 1
]
];
$finalArr = [];
foreach($result as $k => $v) {
$flag = 0;
foreach($finalArr as $kc => $vc){
if($v['size']==$vc['size'] && $v['code']==$vc['code']){
$flag = 1;
$finalArr[$kc]['qn'] = $finalArr[$kc]['qn'] + 1;
break;
}
}
if($flag==0){
$finalArr[] =
[
'size' => $v['size'],
'code' => $v['code'],
'qn' => 1
];
}
}
echo "<pre>";
print_r($finalArr);
The code is tested against your question and i have explained the code. This gives you solution for any number of arrays and with similar array elements with quantity(qn) incremented as you wanted:
<?php
//Compare the 1st and 2nd array
//Compare the 2st and 3rd array
// and so on
function compare($arr1 = array(), $arr2 = array()) {
$result = array_diff($arr1, $arr2);
return $result;
}
$cart = array(
0 => array('size' => 38, 'code' => 5, 'qn' => 1),
1 => array('size' => 37, 'code' => 5, 'qn' => 1),
2 => array('size' => 37, 'code' => 5, 'qn' => 1),
);
$j = 1;
$cart_total_count = count($cart);//Gives the count of your cart
$final_cart = array();//Final array with qn incremented
for($i=0;$i<$cart_total_count; $i++) {
if (!empty($cart[$i+1])) {//If 2nd array is not present then stop the comparision
if (empty(compare($cart[$i], $cart[$i+1]))){
$j++;
$cart[$i]['qn'] = $j;
$final_cart = $cart[$i];
}
}
}var_dump($final_cart);
//Output
//1. If 2 array are same
//array(3) { ["size"]=> int(37) ["code"]=> int(5) ["qn"]=> int(2) }
//2. If no array are same
//array(0) { }?>

Group multidimensional array data based on two column values and sum values of one column in each group

I have an array which is created as a combination of two database queries from two separate databases, it looks similar to:
$arr1 = [
['part' => '1', 'address' => 'aaa', 'type' => '1', 'count' => 5],
['part' => '1', 'address' => 'bbb', 'type' => '1', 'count' => 5],
['part' => '1', 'address' => 'ccc', 'type' => '1', 'count' => 5],
['part' => '2', 'address' => 'aaa', 'type' => '1', 'count' => 5],
['part' => '2', 'address' => 'bbb', 'type' => '1', 'count' => 5],
['part' => '2', 'address' => 'ccc', 'type' => '2', 'count' => 5]
];
I am looking for a way to group this array by part and type values. I also need to know the total of the count values as they are grouped.
The results would be something like:
$arr2 = [
['part' => '1', 'type' => '1', 'count' => 15],
['part' => '2', 'type' => '1', 'count' => 10],
['part' => '2', 'type' => '2', 'count' => 5]
];
but I just can't see how to do this. I have seen a few examples of grouping by a single key/value, but not by multiple values at once.
This function should do the job.
function groupByPartAndType($input) {
$output = Array();
foreach($input as $value) {
$output_element = &$output[$value['part'] . "_" . $value['type']];
$output_element['part'] = $value['part'];
$output_element['type'] = $value['type'];
!isset($output_element['count']) && $output_element['count'] = 0;
$output_element['count'] += $value['count'];
}
return array_values($output);
}
If both databases are on the same database server you would be able to do this using SQLs GROUP BY feature.
The following:
$arr2 = array();
foreach ($arr1 as $a) {
unset($a['address']);
$key = $a['type'] . '-' . $a['part'];
if (isset($arr2[$key])) {
$arr2[$key]['count'] += $a['count'];
} else {
$arr2[$key] = $a;
}
}
$arr2 = array_values($arr2);
Would output
array
0 =>
array
'part' => string '1' (length=1)
'type' => string '1' (length=1)
'count' => int 15
1 =>
array
'part' => string '2' (length=1)
'type' => string '1' (length=1)
'count' => int 10
2 =>
array
'part' => string '2' (length=1)
'type' => string '2' (length=1)
'count' => int 5
Something like
$newarr=array();
foreach ( $arr as $Key => $Value ) {
$newarr[$Value[part]][]=$arr[$key];
}
foreach ( $newarr[part] as $Key => $Value ) {
...
}
Full answer for multi-keys arrays grouping is
// * $arr - associative multi keys data array
// * $group_by_fields - array of fields to group by
// * $sum_by_fields - array of fields to calculate sum in group
function array_group_by($arr, $group_by_fields = false, $sum_by_fields = false) {
if ( empty($group_by_fields) ) return; // * nothing to group
$fld_count = 'grp:count'; // * field for count of grouped records in each record group
// * format sum by
if (!empty($sum_by_fields) && !is_array($sum_by_fields)) {
$sum_by_fields = array($sum_by_fields);
}
// * protected from collecting
$fields_collected = array();
// * do
$out = array();
foreach($arr as $value) {
$newval = array();
$key = '';
foreach ($group_by_fields as $field) {
$key .= $value[$field].'_';
$newval[$field] = $value[$field];
unset($value[$field]);
}
// * format key
$key = substr($key,0,-1);
// * count
if (isset($out[$key])) { // * record already exists
$out[$key][$fld_count]++;
} else {
$out[$key] = $newval;
$out[$key][$fld_count]=1;
}
$newval = $out[$key];
// * sum by
if (!empty($sum_by_fields)) {
foreach ($sum_by_fields as $sum_field) {
if (!isset($newval[$sum_field])) $newval[$sum_field] = 0;
$newval[$sum_field] += $value[$sum_field];
unset($value[$sum_field]);
}
}
// * collect differencies
if (!empty($value))
foreach ($value as $field=>$v) if (!is_null($v)) {
if (!is_array($v)) {
$newval[$field][$v] = $v;
} else $newval[$field][join('_', $v)] = $v; // * array values
}
$out[$key] = $newval;
}
return array_values($out);
}
If this task was necessary in one of my projects, I would craft a snippet that would not need reference variables or any iterated function calls.
Inside the loop, declare the composite temporary key as a variable (since it is used more than once). Push the new row into the result array using the composite key as the temporary first-level key.
Use the null coalescing operator to use the pre-existing count for a given group or zero if the group has not yet been encountered. Then add the new count value to the previously accumulated count.
This technique will unconditionally overwrite the encountered group every time it is repeated. In doing so, the data will be updated with the correct part, type, and count values throughout the iterative process.
When the loop finishes, re-index the result array by calling array_values().
Code: (Demo)
$arr1 = [
['part' => '1', 'address' => 'aaa', 'type' => '1', 'count' => 5],
['part' => '1', 'address' => 'bbb', 'type' => '1', 'count' => 5],
['part' => '1', 'address' => 'ccc', 'type' => '1', 'count' => 5],
['part' => '2', 'address' => 'aaa', 'type' => '1', 'count' => 5],
['part' => '2', 'address' => 'bbb', 'type' => '1', 'count' => 5],
['part' => '2', 'address' => 'ccc', 'type' => '2', 'count' => 5]
];
$result = [];
foreach ($arr1 as $row) {
$compositeKey = $row['part'] . '-' . $row['type'];
$result[$compositeKey] = [
'part' => $row['part'],
'type' => $row['type'],
'count' => ($result[$compositeKey]['count'] ?? 0) + $row['count']
];
}
var_export(array_values($result));
Output:
array (
0 =>
array (
'part' => '1',
'type' => '1',
'count' => 15,
),
1 =>
array (
'part' => '2',
'type' => '1',
'count' => 10,
),
2 =>
array (
'part' => '2',
'type' => '2',
'count' => 5,
),
)
p.s. Ideally, this task probably could/should be performed in the sql but we don't have the details to provide any specific guidance.

Categories