How to merge arrays based on deep value? - php

Array (
[0] => Array (
[OrderProduct] => Array (
[pro_code] => B1
[totalQTY] => 4
)
)
[1] => Array (
[OrderProduct] => Array (
[pro_code] => B2
[totalQTY] => 4
)
)
[2] => Array (
[OrderProduct] => Array (
[pro_code] => B4
[totalQTY] => 4
)
)
)
Array (
[0] => Array (
[OrderProduct] => Array (
[pro_code] => B1
[totalDistSalesQTY] => 360
)
)
[1] => Array (
[OrderProduct] => Array (
[pro_code] => B2
[totalDistSalesQTY] => 600
)
)
[2] => Array (
[OrderProduct] => Array (
[pro_code] => B3
[totalDistSalesQTY] => 600
)
)
)
I would like to merge the two arrays above on the pro_code value. This is the expected result:
Array (
[0] => Array (
[OrderProduct] => Array (
[pro_code] => B1
[totalDistSalesQTY] => 360
[totalQTY] => 4
)
)
[1] => Array (
[OrderProduct] => Array (
[pro_code] => B2
[totalDistSalesQTY] => 600
[totalQTY] => 4
)
)
[2] => Array (
[OrderProduct] => Array (
[pro_code] => B3
[totalDistSalesQTY] => 600
)
)
[3] => Array (
[OrderProduct] => Array (
[pro_code] => B4
[totalQTY] => 4
)
)
)
I want to merge multiple array. Please help me to merge it properly. I applied many techniques to merge it. Still I could not find my way. Please help me with this issue.

So basically, what you have to do, is implement a merge algorithm on a sequence. It is one of the basic programming theses, so it shouldn't be too hard.
First, you have to sort the sequences in ascending order (the arrays in the example) by a unique, strictly sortable value (this is the pro_code in here).
Second, you need to iterate through both sequences at the same time.
If the current items unique key (pro_code) match, merge them and push to the result array, if not push the one with the lesser key to the result array. After pushing, increase the index of the pushed sequence by one.
After reaching the end of one of the sequences, push the rest to the result array.
And this is how it looks implemented in php:
<?php
// Compare func for usort
function compare($a, $b) {
return strcmp($a['OrderProduct']['pro_code'], $b['OrderProduct']['pro_code']);
}
// Sort array with usort
function sort_array(&$a) {
usort($a, 'compare');
}
// Print array
function print_a(&$a) {
print '<pre>';
print_r($a);
print '</pre>';
}
function list_merge(&$a, &$b) {
$resp = array();
$ia = 0;
$ib = 0;
while($ia < count($a) || $ib < count($b)) {
// Check if any of the arrays reached its end
// If not, check for merge
if(isset($a[$ia]) && isset($b[$ib])) {
// Product codes are matching,
// Push merged to $resp
if(strcmp($a[$ia]['OrderProduct']['pro_code'], $b[$ib]['OrderProduct']['pro_code']) == 0) {
$resp[] = array(
'OrderProduct' => array(
'pro_code' => $a[$ia]['OrderProduct']['pro_code'],
'totalQTY' => $a[$ia]['OrderProduct']['totalQTY'] + $b[$ib]['OrderProduct']['totalQTY'],
),
);
// If merge increment both
$ia++;
$ib++;
}
// Product code of element of $a is lesser,
// Push $a to $resp
elseif(strcmp($a[$ia]['OrderProduct']['pro_code'], $b[$ib]['OrderProduct']['pro_code']) < 0) {
$resp[] = array(
'OrderProduct' => array(
'pro_code' => $a[$ia]['OrderProduct']['pro_code'],
'totalQTY' => $a[$ia]['OrderProduct']['totalQTY'],
),
);
// Increment only pushed
$ia++;
}
// Product code of element of $b is lesser,
// Push $b to $resp
else {
$resp[] = array(
'OrderProduct' => array(
'pro_code' => $b[$ib]['OrderProduct']['pro_code'],
'totalQTY' => $b[$ib]['OrderProduct']['totalQTY'],
),
);
// Increment only pushed
$ib++;
}
}
// Else automatically push the existing array
// If $a exists
elseif(isset($a[$ia])) {
$resp[] = array(
'OrderProduct' => array(
'pro_code' => $a[$ia]['OrderProduct']['pro_code'],
'totalQTY' => $a[$ia]['OrderProduct']['totalQTY'],
),
);
// Increment only pushed
$ia++;
}
// Else automatically push the existing array
// If $b exists
else {
$resp[] = array(
'OrderProduct' => array(
'pro_code' => $b[$ib]['OrderProduct']['pro_code'],
'totalQTY' => $b[$ib]['OrderProduct']['totalQTY'],
),
);
// Increment only pushed
$ib++;
}
}
return $resp;
}
// Data structures
$array1 = array(
array(
'OrderProduct' => array(
'pro_code' => 'B1',
'totalQTY' => 4,
),
),
array(
'OrderProduct' => array(
'pro_code' => 'B2',
'totalQTY' => 4,
),
),
array(
'OrderProduct' => array(
'pro_code' => 'B4',
'totalQTY' => 4,
),
),
);
$array2 = array(
array(
'OrderProduct' => array(
'pro_code' => 'B1',
'totalQTY' => 360,
),
),
array(
'OrderProduct' => array(
'pro_code' => 'B2',
'totalQTY' => 600,
),
),
array(
'OrderProduct' => array(
'pro_code' => 'B3',
'totalQTY' => 600,
),
),
);
// Sort arrays by product code
sort_array($array1);
sort_array($array2);
// Merge arrays with list merge
$array3 = list_merge($array1, $array2);
// Print arrays for check
print_a($array1);
print_a($array2);
print_a($array3);
I hope, I could be of any help.

This is as condensed as I can make my method. It is effectively 3 simple steps with a custom function.
Code:
$QTY=[
["OrderProduct"=>
["pro_code"=>"B1","totalQTY"=>"4"]],
["OrderProduct"=>
["pro_code"=>"B2","totalQTY"=>"4"]],
["OrderProduct"=>
["pro_code"=>"B4","totalQTY"=>"4"]]
];
$DSQTY=[
["OrderProduct" =>
["pro_code"=>"B1","totalDistSalesQTY"=>"360"]],
["OrderProduct"=>
["pro_code"=>"B2","totalDistSalesQTY"=>"600"]],
["OrderProduct"=>
["pro_code"=>"B3","totalDistSalesQTY"=>"600"]]
];
function getDeepColumn($a,$c,$result=[]){
foreach(array_column(array_column($a,"OrderProduct"),$c,'pro_code') as $k=>$v){
$result[$k][$c]=$v;
}
return $result;
}
$merged=array_merge_recursive(getDeepColumn($QTY,'totalQTY'),getDeepColumn($DSQTY,'totalDistSalesQTY'));
ksort($merged); // make sure B4 is not before B3
foreach($merged as $k=>$a){
$result[]=["OrderProduct"=>array_merge(["pro_code"=>$k],$a)];
}
var_export($result);
Custom function explanation: getDeepColumn() uses array_column() with a nominated array and a nominated column name to extract the desired values from all OrderProduct subarrays. The column values are temporarily stored in an array of arrays called $result. $result's first level keys are pro_code values for future merging purposes. $result's subarrays consist of the nominated column's name (as key) and the nominated column's value (as values).
First, use array_merge_recursive() to blend together the desired deep column values.
Next, $merge is ksort()'ed (using the unique pro_code keys).
Finally, using foreach(), re-index all OrderProduct subarrays, and return the pro_code value to its rightful place inside the OrderProduct subarray.
Output:
array (
0 =>
array (
'OrderProduct' =>
array (
'pro_code' => 'B1',
'totalQTY' => '4',
'totalDistSalesQTY' => '360',
),
),
1 =>
array (
'OrderProduct' =>
array (
'pro_code' => 'B2',
'totalQTY' => '4',
'totalDistSalesQTY' => '600',
),
),
2 =>
array (
'OrderProduct' =>
array (
'pro_code' => 'B3',
'totalDistSalesQTY' => '600',
),
),
3 =>
array (
'OrderProduct' =>
array (
'pro_code' => 'B4',
'totalQTY' => '4',
),
),
)

This is pretty simple if you use the product code as a key in the result array.
// combine the two arrays together and iterate all their rows
foreach (array_merge($array1, $array2) as $row) {
// get the code
$code = $row['OrderProduct']['pro_code'];
// get the previous value for that code, or an empty array if there isnt' one
$prev = $result[$code]['OrderProduct'] ?? [];
// merge the previous value with the new value
$result[$code]['OrderProduct'] = array_merge($prev, $row['OrderProduct']);
}
You'll end up with product code keys in your result array, which is probably fine. If it's not fine, you can remove them with $result = array_values($result).

Related

How to remove the keys from a multi-dimensional associative array?

I have an associative array which has the following format:
array (
'Shopping and fashion' =>
array (
'childData' =>
array (
'Beauty' =>
array (
'childData' =>
array (
'Cosmetics' =>
array (
'id' => '6002839660079',
'name' => 'Cosmetics',
'parentName' => 'Beauty',
),
'Tattoos' =>
array (
'id' => '6003025268985',
'name' => 'Tattoos',
'parentName' => 'Beauty',
),),))))
I want to remove the keys from the array.
For this I am using :
array_values($my_data)
However, this only works for the topmost level of array, which in this case is "Shopping and fashion". It converts that to index 0.
How can I do the same for every array inside the index too?
EXPECTED OUTPUT:
array (
0 =>
array (
'childData' =>
array (
0 =>
array (
'childData' =>
array (
0 =>
array (
'id' => '6002839660079',
'name' => 'Cosmetics',
'parentName' => 'Beauty',
),
1 =>
array (
'id' => '6003025268985',
'name' => 'Tattoos',
'parentName' => 'Beauty',
),
),
'name' => 'Beauty',
'id' => '6002867432822',
'parentName' => 'Shopping and fashion',
),)))
The array I have has many different arrays on the parent level and many more array inside them so using for each is posing a problem as the number of "childData",i.e child arrays inside parent arrays are varying.
Could recursion be possible?
I don't know built-in function, but maybe this function would help you?
function array_values_recursive( $array ) {
$newArray = [];
foreach ( $array as $key => $value ) {
if ( is_array( $value ) ) {
$newArray[] = array_values_recursive( $value );
} else {
$newArray[$key] = $value;
}
}
return $newArray;
}

How to store a multidimentional array to simple array

I have an Array as seen below.
Array
(
[0] =>
[key-1] => Array
(
[key-1-1] => 33
[key-1-2] => 22
)
[key-2] => -1
[key-3] => Array
(
[data] => Array
(
[other_data] => Array
(
[0] => data1
[1] => data2
)
)
)
[key-4] => data3
[key-5] => data4
)
I need to convert these in simpler form of end values as given below and save to an external php file using file_put_contents . I am trying for hours and I tried var_export, multiple foreach and got some degree of success, but not exactly what I want.
$value['key-1-1'] = '33';
$value['key-1-2'] = '22';
$value['other_data'] = array('data1', 'data2');
$value['key-4'] = 'data3';
$value['key-5'] = 'data4';
Can someone help with achieving it ?
You can get somewhat near to your desired output with array_walk_recursive. The troublesome keys are the numeric ones.
If you only have one other_data sub arrays, you could slightly adjust the output below. You'll get overwritten values for like numeric keys. So this method really depends on your data.
<?php
$data =
array
(
'0' =>'',
'key-1' => array
(
'key-1-1' => 33,
'key-1-2' => 22
),
'key-2' => -1,
'key-3' => array
(
'data' => array
(
'other_data' => array
(
'0' => 'data1',
'1' => 'data2'
)
)
),
'key-4' => 'data3',
'key-5' => 'data4'
);
array_walk_recursive($data, function($v, $k) use (&$result) {
$result[$k] = $v;
});
var_export($result);
Output:
array (
0 => 'data1',
'key-1-1' => 33,
'key-1-2' => 22,
'key-2' => -1,
1 => 'data2',
'key-4' => 'data3',
'key-5' => 'data4',
)

Grouped Array data

I'm trying to figure out how to get data from the following data structure. I managed to group a flat array into the following array, but am finding it difficult to find the proper resources to explain how to get the data out. I'm looking to get the count of the items in each group. Then i'd like to print the ids from the inner array...
array (
'grinds' =>
array (
0 =>
array (
'id' => 16562,
'slug' => 'grinds',
),
1 =>
array (
'id' => 16561,
'slug' => 'grinds',
),
),
'online-grinds' =>
array (
0 =>
array (
'id' => 16566,
'slug' => 'online-grinds',
),
),
)
$my_array1 = array(
'grinds' =>
array(
0 =>
array(
'id' => 16562,
'slug' => 'grinds',
),
1 =>
array(
'id' => 16561,
'slug' => 'grinds',
),
),
'online-grinds' =>
array(
0 =>
array(
'id' => 16566,
'slug' => 'online-grinds',
),
),
);
get counts:
$counts = array();
foreach ($my_array1 as $key => $val) {
$counts[$key] = count($val);
}
var_dump($counts);
yields:
array(2) {
["grinds"]=>
int(2)
["online-grinds"]=>
int(1)
}
get inner ids:
$innerids = array();
foreach ($my_array1 as $key => $val) {
foreach ($val as $key2 => $val2) {
$innerids[] = $val2['id'];
}
}
var_dump($innerids);
yields:
array(3) {
[0]=>
int(16562)
[1]=>
int(16561)
[2]=>
int(16566)
}
You could use array_map and then for every item use array_column and specifing the field name and then use array_count_values to count the unique values.
Then combine those in an array and use the existing key again as the key for then new array.
$arrays = array_map(function($x) {
return [
array_count_values(array_column($x, 'id')),
array_count_values(array_column($x, 'slug'))
];
}, $arrays);
print_r($arrays);
Result
Array
(
[grinds] => Array
(
[0] => Array
(
[16562] => 1
[16561] => 1
)
[1] => Array
(
[grinds] => 2
)
)
[online-grinds] => Array
(
[0] => Array
(
[16566] => 1
)
[1] => Array
(
[online-grinds] => 1
)
)
)
Demo

Group rows in an associative array of associative arrays by column value and preserve the original first level keys

I have an array of subarrays in the following format:
[
'a' => ['id' => 20, 'name' => 'chimpanzee'],
'b' => ['id' => 40, 'name' => 'meeting'],
'c' => ['id' => 20, 'name' => 'dynasty'],
'd' => ['id' => 50, 'name' => 'chocolate'],
'e' => ['id' => 10, 'name' => 'bananas'],
'f' => ['id' => 50, 'name' => 'fantasy'],
'g' => ['id' => 50, 'name' => 'football']
]
And I would like to group it into a new array based on the id field in each subarray.
array
(
10 => array
(
e => array ( id = 10, name = bananas )
)
20 => array
(
a => array ( id = 20, name = chimpanzee )
c => array ( id = 20, name = dynasty )
)
40 => array
(
b => array ( id = 40, name = meeting )
)
50 => array
(
d => array ( id = 50, name = chocolate )
f => array ( id = 50, name = fantasy )
g => array ( id = 50, name = football )
)
)
$arr = array();
foreach ($old_arr as $key => $item) {
$arr[$item['id']][$key] = $item;
}
ksort($arr, SORT_NUMERIC);
foreach($array as $key => $value){
$newarray[$value['id']][$key] = $value;
}
var_dump($newarray);
piece of cake ;)
The following code adapts #Tim Cooper’s code to mitigate Undefined index: id errors in the event that one of the inner arrays doesn’t contain an id:
$arr = array();
foreach($old_arr as $key => $item)
{
if(array_key_exists('id', $item))
$arr[$item['id']][$key] = $item;
}
ksort($arr, SORT_NUMERIC);
However, it will drop inner arrays without an id.
E.g.
$old_arr = array(
'a' => array ( 'id' => 20, 'name' => 'chimpanzee' ),
'b' => array ( 'id' => 40, 'name' => 'meeting' ),
'c' => array ( 'id' => 20, 'name' => 'dynasty' ),
'd' => array ( 'id' => 50, 'name' => 'chocolate' ),
'e' => array ( 'id' => 10, 'name' => 'bananas' ),
'f' => array ( 'id' => 50, 'name' => 'fantasy' ),
'g' => array ( 'id' => 50, 'name' => 'football' ),
'h' => array ( 'name' => 'bob' )
);
will drop the 'h' array completely.
You can also use Arrays::groupBy() from ouzo-goodies:
$groupBy = Arrays::groupBy($array, Functions::extract()->id);
print_r($groupBy);
And result:
Array
(
[20] => Array
(
[0] => Array
(
[id] => 20
[name] => chimpanzee
)
[1] => Array
(
[id] => 20
[name] => dynasty
)
)
[40] => Array
(
[0] => Array
(
[id] => 40
[name] => meeting
)
)
[50] => Array
(
[0] => Array
(
[id] => 50
[name] => chocolate
)
[1] => Array
(
[id] => 50
[name] => fantasy
)
[2] => Array
(
[id] => 50
[name] => football
)
)
[10] => Array
(
[0] => Array
(
[id] => 10
[name] => bananas
)
)
)
And here are the docs for Arrays and Functions.
Here is a function that will take an array as the first argument and a criteria (a string or callback function) as the second argument. The function returns a new array that groups the array as asked for.
/**
* Group items from an array together by some criteria or value.
*
* #param $arr array The array to group items from
* #param $criteria string|callable The key to group by or a function the returns a key to group by.
* #return array
*
*/
function groupBy($arr, $criteria): array
{
return array_reduce($arr, function($accumulator, $item) use ($criteria) {
$key = (is_callable($criteria)) ? $criteria($item) : $item[$criteria];
if (!array_key_exists($key, $accumulator)) {
$accumulator[$key] = [];
}
array_push($accumulator[$key], $item);
return $accumulator;
}, []);
}
Here is the given array:
$arr = array(
'a' => array ( 'id' => 20, 'name' => 'chimpanzee' ),
'b' => array ( 'id' => 40, 'name' => 'meeting' ),
'c' => array ( 'id' => 20, 'name' => 'dynasty' ),
'd' => array ( 'id' => 50, 'name' => 'chocolate' ),
'e' => array ( 'id' => 10, 'name' => 'bananas' ),
'f' => array ( 'id' => 50, 'name' => 'fantasy' ),
'g' => array ( 'id' => 50, 'name' => 'football' )
);
And examples using the function with a string and a callback function:
$q = groupBy($arr, 'id');
print_r($q);
$r = groupBy($arr, function($item) {
return $item['id'];
});
print_r($r);
The results are the same in both examples:
Array
(
[20] => Array
(
[0] => Array
(
[id] => 20
[name] => chimpanzee
)
[1] => Array
(
[id] => 20
[name] => dynasty
)
)
[40] => Array
(
[0] => Array
(
[id] => 40
[name] => meeting
)
)
[50] => Array
(
[0] => Array
(
[id] => 50
[name] => chocolate
)
[1] => Array
(
[id] => 50
[name] => fantasy
)
[2] => Array
(
[id] => 50
[name] => football
)
)
[10] => Array
(
[0] => Array
(
[id] => 10
[name] => bananas
)
)
)
Passing the callback is overkill in the example above, but using the callback finds its use when you pass in an array of objects, a multidimensional array, or have some arbitrary thing you want to group by.
Maybe it's worth to mention that you can also use php array_reduce function
$items = [
['id' => 20, 'name' => 'chimpanzee'],
['id' => 40, 'name' => 'meeting'],
['id' => 20, 'name' => 'dynasty'],
['id' => 50, 'name' => 'chocolate'],
['id' => 10, 'name' => 'bananas'],
['id' => 50, 'name' => 'fantasy'],
['id' => 50, 'name' => 'football'],
];
// Grouping
$groupedItems = array_reduce($items, function ($carry, $item) {
$carry[$item['id']][] = $item;
return $carry;
}, []);
// Sorting
ksort($groupedItems, SORT_NUMERIC);
print_r($groupedItems);
https://www.php.net/manual/en/function.array-reduce.php
Because of how PHP's sorting algorithm treats multidimensional arrays -- it sorts by size, then compares elements one at a time, you can actually use a key-preserving sort on the input BEFORE restructuring. In functional style programming, this means that you don't need to declare the result array as a variable.
Code: (Demo)
asort($array);
var_export(
array_reduce(
array_keys($array),
function($result, $k) use ($array) {
$result[$array[$k]['id']][$k] = $array[$k];
return $result;
}
)
);
I must say that functional programming is not very attractive for this task because the first level keys must be preserved.
Although array_walk() is more succinct, it still requires the result array to be passed into the closure as a reference variable. (Demo)
asort($array);
$result = [];
array_walk(
$array,
function($row, $k) use (&$result) {
$result[$row['id']][$k] = $row;
}
);
var_export($result);
I'd probably recommend a classic loop for this task. The only thing the loop needs to do is rearrange the first and second level keys. (Demo)
asort($array);
$result = [];
foreach ($array as $k => $row) {
$result[$row['id']][$k] = $row;
}
var_export($result);
To be completely honest, I expect that ksort() will be more efficient than pre-loop sorting, but I wanted to a viable alternative.

Which PHP Array function should I use?

I have two arrays:
Array
(
[0] => Array
(
[id] => 1
[type] => field
[remote_name] => Title
[my_name] => title
[default_value] => http%3A%2F%2Ftest.com
)
[1] => Array
(
[id] => 2
[type] => field
[remote_name] => BookType
[my_name] => book-type
[default_value] =>
)
[2] => Array
(
[id] => 3
[type] => value
[remote_name] => dvd-disc
[my_name] => dvd
[default_value] =>
)
)
Array
(
[title] => Test
[book-type] => dvd
)
I need to take each key in the second array, match it with the my_name value in the first array and replace it with the corresponding remote_name value of the first array while preserving the value of the second array.
There's got to be some carrayzy function to help!
EDIT: There will also be a few cases that the value of the second array will need to be replaced by the value of the first array's remote_name where the value of the second array matches the value of the first array's my_name. How can I achieve this?
EG: book-type => dvd should turn into BookType => dvd-disc
Like so?:
$first = array(
array(
'id' => 1,
'type' => 'field',
'remote_name' => 'Title',
'my_name' => 'title',
'default_value' => 'http%3A%2F%2Ftest.com',
),
array(
'id' => 2,
'type' => 'field',
'remote_name' => 'BookType',
'my_name' => 'book-type',
'default_value' => '',
),
array(
'id' => 3,
'type' => 'value',
'remote_name' => 'dvd-disc',
'my_name' => 'dvd',
'default_value' => '',
),
);
$second = array(
'title' => 'Test',
'book-type' => 'dvd',
);
$map = array('fields' => array(), 'values' => array());
foreach ($first as $entry) {
switch ($entry['type']) {
case 'field':
$map['fields'][$entry['my_name']] = $entry['remote_name'];
break;
case 'value':
$map['values'][$entry['my_name']] = $entry['remote_name'];
break;
}
}
$new = array();
foreach ($second as $key => $val) {
$new[isset($map['fields'][$key]) ? $map['fields'][$key] : $key] = isset($map['values'][$val]) ? $map['values'][$val] : $val;
}
print_r($new);
Output:
Array
(
[Title] => Test
[BookType] => dvd-disc
)
Explanation:
The first loop collects the my_name/remote_name pairs for fields and values and makes them more accessible.
Like so:
Array
(
[fields] => Array
(
[title] => Title
[book-type] => BookType
)
[values] => Array
(
[dvd] => dvd-disc
)
)
The second loop will traverse $second and use the key/value pairs therein to populate $new. But while doing so will check for key/value duplicates in $map.
Keys or values not found in the map will be used as is.
foreach($arr1 as &$el) {
$el['remote_name'] = $arr2[$el['my_name']];
}
unset($el);
I am not aware of such a carrayzy function, but I know how you could do it:
//$array1 is first array, $array2 is second array
foreach($array1 as $key => $value){
if (isset($value['remote_name'], $value['my_name']) && $value['remote_name'] && $value['my_name']){
$my_name = $value['my_name'];
if (isset($array2[$my_name])) {
$remote_name = $value['remote_name'];
$array2[$remote_name] = $array2[$my_name];
//cleanup
unset($array2[$my_name]);
}
}
}

Categories