Merge rows of two arrays containing objects by first level index - php

I am trying to merge the results of two CodeIgniter queries. The trouble is that the two arrays contain rows of objects and array_merge() does not work on objects. How can I merge the two object arrays.
Input:
$array1 = [
(object) [
'trainerid' => 1,
'firstname' => 'abc',
'location' => 'area',
'photo' => 'abc.jpg',
'role' => 'user',
'city' => 'bangalore',
],
(object) [
'trainerid' => 2,
'firstname' => 'abcd',
'location' => 'area',
'photo' => 'abcd.jpg',
'role' => 'user',
'city' => 'bangalore',
],
];
$array2 = [
(object) [
'rating' => 3.0000,
'users' => 0,
'review' => 0
],
(object) [
'rating' => 4.0000,
'users' => 4,
'review' => 5
]
];
Desired output:
array (
0 =>
(object) array(
'trainerid' => 1,
'firstname' => 'abc',
'location' => 'area',
'photo' => 'abc.jpg',
'role' => 'user',
'city' => 'bangalore',
'rating' => 3.0,
'users' => 0,
'review' => 0,
),
1 =>
(object) array(
'trainerid' => 2,
'firstname' => 'abcd',
'location' => 'area',
'photo' => 'abcd.jpg',
'role' => 'user',
'city' => 'bangalore',
'rating' => 4.0,
'users' => 4,
'review' => 5,
),
)

You've made a relatively basic task much harder to do by using CodeIgniter's ->result() instead of ->result_array(). If you had arrays of arrays, then you could just use array_map('array_merge', $array1, $array2), but that will not work with your object arrays.
Instead, you'll need to call get_object_vars() on each object (and you are going to be iterating multiple objects inside the outer array_map().
Essentially, this task is a matter of iterating both arrays simultaneously, casting each pair of objects to array type, then merging the arrays, then casting the merged array back to object type.
The following snippet is designed with the flexibility to process 2 or more object arrays.
Code: (Demo)
var_export(
array_map(
fn(...$objs) => (object) array_merge(...array_map('get_object_vars', $objs)),
$array1,
$array2
)
);
The above can be written a little more concisely when only two object arrays are being merged. (Demo)
var_export(
array_map(
fn($obj1, $obj2) => (object) array_merge((array) $obj1, (array) $obj2),
$array1,
$array2
)
);
A classic nested foreach approach can be used to avoid casting and recasting data types while merging. array_merge() and array_merge_recursive() cannot (currently) be used to merge objects together -- their properties must be individually defined in the receiving object. (Demo)
$result = [];
foreach ($array1 as $i => $obj1) {
$result[$i] = $obj1;
foreach ($array2[$i] as $k => $v) {
$result[$i]->$k = $v;
}
}
var_export($result);
Output from any of the above snippets:
array (
0 =>
(object) array(
'trainerid' => 1,
'firstname' => 'abc',
'location' => 'area',
'photo' => 'abc.jpg',
'role' => 'user',
'city' => 'bangalore',
'rating' => 3.0,
'users' => 0,
'review' => 0,
),
1 =>
(object) array(
'trainerid' => 2,
'firstname' => 'abcd',
'location' => 'area',
'photo' => 'abcd.jpg',
'role' => 'user',
'city' => 'bangalore',
'rating' => 4.0,
'users' => 4,
'review' => 5,
),
)

You need to use array_map along with the array_merge like as
print_r(array_map('array_merge', $arr1, $arr2));

You can have something like :
Let's assume $a is your first array and $b is the second one and $c will be your desired output.
$c = array();
for($i=0;$i<count($a);$i++){
$c[$i] = array_merge($a[$i],$b[$i]);
}

Related

How to convert a Single Array into a multidimensional array in PHP using letters

I need to generate a multidimensional array based on a "map" of letters
my array :
$list = [
0 => [
'name' => 'blah',
'path' => 'A'
],
1 => [
'name' => 'blah',
'path' => 'AA'
],
2 => [
'name' => 'blah',
'path' => 'AB'
],
3 => [
'name' => 'blah',
'path' => 'B'
],
4 => [
'name' => 'blah',
'path' => 'BA'
],
5 => [
'name' => 'blah',
'path' => 'BAA'
],
];
but I need this:
$list = [
0 => [
'name' => 'blah',
'path' => 'A',
'childs' => [
0 => [
'name' => 'blah',
'path' => 'AA'
],
1 => [
'name' => 'blah',
'path' => 'AB'
],
]
],
3 => [
'name' => 'blah',
'path' => 'B',
'childs' => [
0 => [
'name' => 'blah',
'path' => 'BA',
'childs' => [
0 => [
'name' => 'blah',
'path' => 'BAA'
],
]
],
]
],
];
I'm going to need this array to be in a way that is easy to manipulate, but I'm not able to use the "&" in a foreach so that I can generate an array at least close to what I put above.
sorry if i asked the question incorrectly... my english is bad and it's my first time here
I would loop through the $list giving each item a ['childs'] array then copy into a $new_list based on the path using the letters as keys in the array. I accomplish this below by constructing a string to evaluate with eval().
$new_list = [];
foreach( $list as $item ){
$item['childs'] = [];
$ev = '$new_list';
foreach(str_split($item['path']) as $i => $p){
if(0 == $i){
$ev .= "['$p']"; // $new_list['A']
}else{
$ev .= "['childs']['$p']"; // $new_list['A']['childs']['A'] ... ['childs']['B'] etc.
}
}
$ev .= ' = $item;'; // $new_list['A']['childs']['A'] = $item;
eval($ev);
}
var_export($new_list);
The above code outputs the following:
array (
'A' =>
array (
'name' => 'blah',
'path' => 'A',
'childs' =>
array (
'A' =>
array (
'name' => 'blah',
'path' => 'AA',
'childs' =>
array (
),
),
'B' =>
array (
'name' => 'blah',
'path' => 'AB',
'childs' =>
array (
),
),
),
),
'B' =>
array (
'name' => 'blah',
'path' => 'B',
'childs' =>
array (
'A' =>
array (
'name' => 'blah',
'path' => 'BA',
'childs' =>
array (
'A' =>
array (
'name' => 'blah',
'path' => 'BAA',
'childs' =>
array (
),
),
),
),
),
),
)
See it in action here: https://onlinephp.io/c/cf8d0
Note:
This works because you have all ancestor-items defined in $list before their descendants. If the order of items varies (like 'AAB' before 'AA') then you will need to add some checks to see if the $new_list['A']['childs']['A']['childs'] array exists yet and construct it when necessary.
This recursive approach seeks out qualifying rows, pushes them as children of the desired parent row and unsets all rows that are used as children of a parent (somewhere). The new array structure is not returned; the input array is modified by reference.
Code: (Demo)
function rowsToTree(&$array, $parent = '') {
$children = [];
foreach ($array as $i => &$row) {
if ($parent === substr($row['path'], 0, -1)) {
// recurse for children
$found = rowsToTree($array, $row['path']);
// only declare the children element if has children
if ($found) {
$row['children'] = $found;
// if not a top-level parent remove the row from the original array which has been pushed into the children element
if ($parent) {
unset($array[$i]);
}
}
// collect all children in this level for this parent (not top level) to be returned to 8ts parent then remove row from original array
if ($parent) {
$children[] = $row;
unset($array[$i]);
}
}
}
return $children;
}
rowsToTree($rows);
var_export($rows);
Here is a non-recursive approach that does provides the result in a new variable by sorting the array with deepest children first then using a nested loop to brute-force search for parents.
Code: (Demo)
$copy = $rows;
uasort($rows, fn($a, $b) => $b['path'] <=> $a['path']);
foreach ($rows as $i => $row1) {
foreach ($rows as $j => &$row2) {
if (substr($row1['path'], 0, -1) === $row2['path']) {
$copy[$j]['children'][] = $row1;
unset($copy[$i]);
}
}
}
var_export($copy);
my solution:
$list = [
['name' => 'blah1', 'path' => 'A'],
['name' => 'blah2', 'path' => 'AA'],
['name' => 'blah3', 'path' => 'AB'],
['name' => 'blah4', 'path' => 'B'],
['name' => 'blah5', 'path' => 'BA'],
['name' => 'blah5', 'path' => 'BB'],
['name' => 'blah6', 'path' => 'BAA'],
['name' => 'blah6', 'path' => 'BAB'],
['name' => 'blah6', 'path' => 'BAC'],
];
$data = [];
$arr = &$data;
foreach ($list as $item){
$letters = str_split($item['path']);
$i = 1;
foreach ($letters as $letter){
$path = substr($item['path'], 0, $i);
if (!isset($arr[$path])){
$arr[$path] = $item;
$arr[$path]['childs'] = [];
}
$arr = &$arr[$path]['childs'];
$i++;
}
$arr = &$data;
}
Because there weren't much clues on the question on how to actually do the desired grouping. I'll assume, based on the provided output sample, that the grouping will be based on the first letter found in the values where the key is path. The task will basically be solved using array_reduce() built-in function:
Iteratively reduce the array to a single value using a callback function. Source: php.net
To maintain a performant solution, we'll index the resulting array based on the letters we found (the letters we'll group using them). Like so we will iterate over the $list array only once.
The trick here is to decide what to do at every iteration of the array_reduce function:
When it's the first time we encounter a new letter, let's call that letter $letter, then we will construct an array (based on your output sample) where $letter is the key.
Otherwise, if we have already the letter in the resulting array we will simply merge the children of the resulting array of the $letter key.
Using the letters as keys will drastically improve the solution's performance because we can simply say $result[$letter] and this will immediately return the array found on the $letter key (O(1)).
To illustrate, here's a code sample that should get the job done:
$list = [
['name' => 'blah', 'path' => 'A'],
['name' => 'blah', 'path' => 'AA'],
['name' => 'blah', 'path' => 'AB'],
['name' => 'blah', 'path' => 'B'],
['name' => 'blah', 'path' => 'BA'],
['name' => 'blah', 'path' => 'BAA'],
];
$result = array_reduce($list, function ($a, $c) {
$l = substr($c['path'], 0, 1);
isset($a[$l])
? $a[$l]['children'][] = $c
: $a[$l] = [
'name' => $c['name'],
'path' => $c['path'],
'children' => []
];
return $a;
}, []);
The $result array will have the letters as keys, if you need to remove those letters and use numerical keys, you may call
And I have made a live demo too.

While destructuring an array, can the same element value be accessed more than once?

Since PHP7.1, a foreach() expression can implement array destructuring as a way of unpacking row values and make individualized variable assignments.
When using array destructuring within a foreach() loop, can a specific value be accessed by its associative key more than once?
For example:
$array = [
['group' => 'A', 'name' => 'Ann', 'age' => 19],
['group' => 'B', 'name' => 'Bea', 'age' => 26],
['group' => 'A', 'name' => 'Cam', 'age' => 32],
['group' => 'B', 'name' => 'Des', 'age' => 24]
];
I know I can use:
foreach ($array as ['group' => $group, 'name' => $name, 'age' => $age]) {
// do stuff with $group, $name, and $age
}
But what if I want to access, say, $group a second time while destructuring? Is it possible?
It looks pretty unorthodox and there will be very few scenarios when it is useful, but yes it is possible/valid.
Just repeat the "key => value" syntax again and provide a different variable in the value position. In this context, the keys may be repeated.
Here is a demonstration of using array destructuring to "pivot" a result set with a body-less foreach loop.
Code: (Demo)
$array = [
['group' => 'A', 'name' => 'Ann', 'age' => 19],
['group' => 'B', 'name' => 'Bea', 'age' => 26],
['group' => 'A', 'name' => 'Cam', 'age' => 32],
['group' => 'B', 'name' => 'Des', 'age' => 24]
];
$result = [];
foreach ($array as ['group' => $group, 'group' => $result[$group]['group'], 'name' => $name, 'age' => $result[$group][$name]]);
# 1st assignment of group value^^^^^^
# 2nd assignment of group value-------------------^^^^^^^^^^^^^^^^^^^^^^^^
var_export($result);
Output:
array (
'A' =>
array (
'group' => 'A',
'Ann' => 19,
'Cam' => 32,
),
'B' =>
array (
'group' => 'B',
'Bea' => 26,
'Des' => 24,
),
)
The same technique works outside of the context of a foreach() loop as well. (Demo)
['one' => $result[], 'one' => $result[]] = ['one' => 1];
var_export($result);
Output:
array (
0 => 1,
1 => 1,
)

Merge/Flatten 3rd level data to create an array of arrays

I have an array with 3 levels and I'd like to merge/flatten all 3rd level subarrays into one subarray/row on the 2nd level.
$array = [
[
'User' => [
'id' => 57341,
'updated' => null,
'userId' => 57341,
'value' => null,
'lat' => 53.4537812,
'lon' => -2.1792437,
],
[
'feed_likes' => 'NA',
'category_idx' => -1,
'type' => 'User'
]
],
[
'User' => [
'id' => 57336,
'updated' => null,
'userId' => 57336,
'value' => null,
'lat' => 53.473684,
'lon' => -2.2399827,
],
[
'feed_likes' => 'NA',
'category_idx' => -1,
'type' => 'User'
]
],
];
The deep User-keyed subarrays (having 6 elements) should be merged with its sibling/indexed subarray (having 3 elements) to form a 9-element row on the second level.
Desired result:
[
[
'id' => 57341,
'updated' => null,
'userId' => 57341,
'value' => null,
'lat' => 53.4537812,
'lon' => -2.1792437,
'feed_likes' => 'NA',
'category_idx' => -1,
'type' => 'User'
],
[
'id' => 57336,
'updated' => null,
'userId' => 57336,
'value' => null,
'lat' => 53.473684,
'lon' => -2.2399827,
'feed_likes' => 'NA',
'category_idx' => -1,
'type' => 'User'
]
]
You can use splat ... operator with array_merge
foreach($a as $child){
$flatten[] = array_merge(...$child);
}
Working example :- https://3v4l.org/HkUh6
To merge and flatten the 3rd level data sets into consolidated 2nd level rows with functional style programming, make iterated calls of array_merge() which receive all 3rd level payloads at once. The spread operator (...) is a concise technique used to unpack multiple elements in an array. A special consideration is needed for this case because spreading elements which have non-numeric keys will cause code breakage. To overcome this, simply call array_values() to "index" the array (replace all keys with sequenial numbers) before spreading.
Code: (Demo)
var_export(
array_map(
fn($rows) => array_merge(...array_values($rows)),
$array
)
);
Slightly different to your example output, but you can merge your inner arrays.
<?php
$data =
[
[
'animal' => [
'type' => 'fox'
],
[
'colour' => 'orange'
]
],
[
'animal' => [
'type' => 'panda'
],
[
'colour' => 'black and white'
]
]
];
$result =
array_map(
function($v) {
return array_merge($v['animal'], $v[0]);
},
$data
);
var_export($result);
Output:
array (
0 =>
array (
'type' => 'fox',
'colour' => 'orange',
),
1 =>
array (
'type' => 'panda',
'colour' => 'black and white',
),
)
If I understood you correctly you need to merge User array and in this case with the second array in that key. it that case something like this should work
foreach($array as $key=>$deep1){
$newArray = [];
foreach($deep1 as $deep2){
$newArray = array_merge($newArray,$deep2)
}
$array[$key] = $newArray;
}
Did II understand your question correctly?

How to extract an array with multiple array keys and values to another array?

Please note that my php version is not 7.
I have an array of arrays like:
array(
'0' => array('id'=>1,'name'=>'abc',"class"=>'xyz'),
'1' => array('id'=>2,'name'=>'abc1',"class"=>'xyz1'),
'2' => array('id'=>3,'name'=>'abc',"class"=>'xyz2'),
);
I want to extract it into two arrays. like
array(
'0' => array('id'=>1,'name'=>'abc'),
'1' => array('id'=>2,'name'=>'abc1'),
'2' => array('id'=>3,'name'=>'abc'),
);
array(
'0' => array('id'=>1,"class"=>'xyz'),
'1' => array('id'=>2,"class"=>'xyz1'),
'2' => array('id'=>3,"class"=>'xyz2'),
);
How can i achieve this, I am in search of some built-in function etc, I studied its supported with array_column but with versions higher than 7.
Edit:
I also tried array_intersect_key and array_slice but its working with single dimensional array.
Then you might want to keep it simple and just use a straight forward foreach loop like this
$old = array(
array('id'=>1,'name'=>'abc',"class"=>'xyz'),
array('id'=>2,'name'=>'abc1',"class"=>'xyz1'),
array('id'=>3,'name'=>'abc',"class"=>'xyz2')
);
foreach ( $old as $temp ) {
$new1 = array('id' => $temp['id'], 'name' => $temp['name']);
$new2 = array('id' => $temp['id'], 'class' => $temp['class']);
}
Use a foreach and add the values to a new array for example:
$idsNames = [];
$idsClasses = [];
$items = [
array('id' => 1, 'name' => 'abc', "class" => 'xyz'),
array('id' => 2, 'name' => 'abc1', "class" => 'xyz1'),
array('id' => 3, 'name' => 'abc', "class" => 'xyz2'),
];
foreach ($items as $item) {
$idsNames[] = ["id" => $item["id"], "name" => $item["name"]];
$idsClasses[] = ["id" => $item["id"], "class" => $item["class"]];
}

multidimensional array merge operation inside loop

let's say I have two arrays like so:
$array1 = array('A' => array(
'B' => array(
'C' => array(
'D' => array(
'data' => array(
0 => array(
'id' => 1,
'name' => 'name 1'),
1 => array(
'id' => 2,
'name' => 'name 2')))))));
$array2 = array('A' => array(
'B' => array(
'C' => array(
'E' => array(
'data' => array(
0 => array(
'id' => 3,
'name' => 'name 3'),
1 => array(
'id' => 4,
'name' => 'name 4')))))));
As you can see, the two arrays have the same key A, B, and C but the keys are different afterwards. How do I merge these two arrays into something like this:
$final_array = array('A' => array(
'B' => array(
'C' => array(
'D' => array(
'data' => array(
0 => array(
'id' => 1,
'name' => 'name 1'),
1 => array(
'id' => 2,
'name' => 'name 2'))),
'E' => array(
'data' => array(
0 => array(
'id' => 3,
'name' => 'name 3'),
1 => array(
'id' => 4,
'name' => 'name 4')))))));
As you can see, in this case I merge the arrays together into the same array that contains different keys for both. In order words, here I'm putting the array starting from key E from the second array into the array with index C.
Any help will be appreciated, thanks
EDIT: Now, how about if my arrays ($array1, $array2, $array3, $array4, etc...) are generated inside a foreach loop, how do I merge all of those arrays together (Notice that I do not know the number of arrays beforehand)
http://php.net/manual/en/function.array-merge-recursive.php
print_r(array_merge_recursive($array1, $array2));
This should do the trick.
Added:
$collection=array();
foreach() {
$collection[]=$myArray; //here you add your array to collection
}
print_r(call_user_func_array('array_merge_recursive', $collection));
i have not tested this but try this code:
foreach( $array1 as $key => $val )
{
if( !in_array( $key, $array2 ) )
{
$array2[$key] = $val;
}
}
EDIT
use Rok Kralj's answer, using native functions are probably the best way to do this as they are much faster.

Categories