Merge collection of nested array elements into one array - php

I'm trying to merge a collection of nested array elements into one array.
Array:
crop_data = [
[
["crop" => "soy"] // 0
],
[
["crop" => "rye"] // 1
],
[
["crop" => "tree"] // 2
]
],
[
[
["crop" => "salt"] // 0
],
[
["crop" => "farm"] // 1
]
],
[
[
["year" => "2015"]
]
]
I've tried the following...
$crop_data = array(); // new array
foreach($crop_list as $value) {
$crop_data = array_merge($value, $crop_list));
}
I would like to merge the inner elements of the three arrays into one array. Any tips on how to achieve this?

You can use array_walk_recursive for this.
$merged = array();
array_walk_recursive($crop_data, function($v, $k) use (&$merged) {
$merged[$k][] = $v;
});

Related

Is there a way to explode arrays of associative arrays into one array without loosing the keys in PHP?

I have an array of arrays of associative arrays like this
$my_array = [
[ 8 => "One" ],
[ 3=>"Two" ]
]
How can I like explode all the associative arrays into one array like this
[
8 => "One",
3 =>"Two"
]
I tried using array_merge(...$my_array) and all it gives is
[
0 => "One"
1 => "Two"
]
in plain php, and assuming those arrays will always look like that:
(if you're not sure you should loop through the sub_array to get the keys)
foreach($my_array as $sub_array ){
$new_array[key($sub_array)] = $sub_array[key($sub_array)];
}
var_export($new_array);
This is how you can do it, by using Collection and the mapWithKeys method :
$a = [
[
8 => "One",
],
[
3 => "Two",
],
];
$b = collect($a)->mapWithKeys(function($a) {
return $a;
})->toArray();
dd($b);
// [
// 8 => "One"
// 3 => "Two"
// ]
Loop through both levels to get the keys
foreach ($my_array as $sub_array) {
foreach ($sub_array as $key => $value) {
$new_array[$key] = $value;
}
}
print_r($new_array);

PHP array diff: merge arrays recursively and show 'new' vs 'old' values in result

I would like to merge two arrays to compare old vs new values. For example, $arr1 is old values $arr2 is new values.
In case when the data is deleted $arr2 is an empty array. Example:
$arr1 = [
"databases" => [
0 => [
"id" => 1
"name" => "DB1"
"slug" => "db1"
"url" => "https://www.db1.org"
]
]
];
$arr2 = [];
For this my expected output after merge is
$merged = [
"databases" => [
0 => [
"id" => [
'old' => 1,
'new' => null
],
"name" => [
'old' => "DB1",
'new' => null
],
"slug" => [
'old' => "db1",
'new' => null
],
"url" => [
'old' => "https://www.db1.org",
'new' => null
],
]
]
];
if arr2 is different then the values should be present in the new field instead of null.
For example:
$arr1 = [
"databases" => [
0 => [
"id" => 1
"name" => "DB1"
"slug" => "db1"
"url" => "https://www.db1.org"
]
]
];
$arr2 = [
"databases" => [
0 => [
"id" => 5
"name" => "DB2"
"slug" => "db2"
"url" => "https://www.db2.com"
]
]
];
expected output:
$merged = [
"databases" => [
0 => [
"id" => [
'old' => 1,
'new' => 5
],
"name" => [
'old' => "DB1",
'new' => "DB2"
],
"slug" => [
'old' => "db1",
'new' => "db2"
],
"url" => [
'old' => "https://www.db1.org",
'new' => "https://www.db2.com"
],
]
]
];
Case 3 is when $arr1 is empty but $arr2 is populated:
$arr1 = [];
$arr2 = [
"databases" => [
0 => [
"id" => 1
"name" => "DB1"
"slug" => "db1"
"url" => "https://www.db1.org"
]
]
];
and the expected output is:
$merged = [
"databases" => [
0 => [
"id" => [
'old' => null,
'new' => 1
],
"name" => [
'old' => null,
'new' => "DB1"
],
"slug" => [
'old' => null,
'new' => "db1"
],
"url" => [
'old' => null,
'new' => "https://www.db1.org"
],
]
]
];
The inbuilt php functions cannot format the data in old vs new format so was wondering how to go about this? Any solutions/suggestions would be appreciated.
Update
Here is what I had tried before:
I had tried simple array_merge_recursive but it does not store the source array. So if you have $arr1 key not there, the final merged array will only have one value.
I tried some more recursive functions late in the night but failed so in essence didn't have anything to show for what I had tried. However, this morning, I came up with the solution and have posted it as an answer in case anyone needs to use it.
Interesting question, as long as a (non-empty) array on one side means to traverse into it and any skalar or null is a terminating node (while if any of old or new being an array would enforce traversing deeper so dropping the other non-array value):
It works by mapping both old and new on one array recursively and when the decision is to make to traverse to offer null values in case a keyed member is not available while iterating over the super set of the keys of both while null would represent no keys:
$keys = array_unique(array_merge(array_keys($old ?? []), array_keys($new ?? [])));
$merged = [];
foreach ($keys as $key) {
$merged['old'] = $old[$key] ?? null;
$merged['new'] = $new[$key] ?? null;
}
This then can be applied recursively, for which I found it is easier to handle both $old and $new as ['old' => $old, 'new' => $new] for that as then the same structure can be recursively merged:
function old_and_new(array $old = null, array $new = null): array
{
$pair = get_defined_vars();
$map =
static fn(callable $map, array $arrays): array => in_array(true, array_map('is_array', $arrays), true)
&& ($parameter = array_combine($k = array_keys($arrays), $k))
&& ($keys = array_keys(array_flip(array_merge(...array_values(array_map('array_keys', array_filter($arrays, 'is_array'))))))
)
? array_map(
static fn($key) => $map($map, array_map(static fn($p) => $arrays[$p][$key] ?? null, $parameter)),
array_combine($keys, $keys)
)
: $arrays;
return $map($map, $pair);
}
print_r(old_and_new(new: $arr2));
Online demo: https://3v4l.org/4KdLs#v8.0.9
The inner technically works with more than two arrays, e.g. three. And it "moves" the array keys upwards, similar to a transpose operation. Which btw. there is a similar question (but only similar, for the interesting part in context of your question it is not answered and my answer here doesn't apply there directly):
Transposing multidimensional arrays in PHP
After reviewing my own code here is the solution I came up with. I am posting it here in case someone else needs a solution for this:
/**
* Function to merge old and new values to create one array with all entries
*
* #param array $old
* #param array $new
* #return void
*/
function recursiveMergeOldNew($old, $new) {
$merged = array();
$array_keys = array_keys($old) + array_keys($new);
if($array_keys) {
foreach($array_keys as $key) {
$oldChildArray = [];
$newChildArray = [];
if(isset($old[$key])) {
if(!is_array($old[$key])) {
$merged[$key]['old'] = $old[$key];
} else {
$oldChildArray = $old[$key];
}
} else {
$merged[$key]['old'] = null;
}
if(isset($new[$key])) {
if( !is_array($new[$key])) {
$merged[$key]['new'] = $new[$key];
} else {
$newChildArray = $new[$key];
}
} else {
$merged[$key]['new'] = null;
}
if($oldChildArray || $newChildArray) {
$merged[$key] = recursiveMergeOldNew($oldChildArray, $newChildArray);
}
}
}
return $merged;
}
Note - this solution needs testing.

PHP get value from armultidimensional array based on array with keys [duplicate]

This question already has answers here:
How to access and manipulate multi-dimensional array by key names / path?
(10 answers)
Closed 2 years ago.
I'm trying to get reach a point in a dynamicly generated multidimensional array based on a array with keys.
Basicly I have the following array:
$arr = [
"something" => [
'something_else' => [
"another_thing" => "boo"
]
],
"something2" => [
'something_elseghf' => [
"another_thingfg" => [
"hi" => "bye"
]
]
],
"info" => [
'something_else2' => [
"another_thingh" => "boo"
]
],
];
Now I want to set a value in the array based on the keys in a different array:
$keyArr = ["something2", 'something_elseghf' "another_thingfg", "hi"];
So the above array means that I need to set the hi key to some value. How can I reach that part of the array with these random keys, note that the length of $keyArr is dynamic aswell. So I can't reach it with:
$arr[$keyArr[0]][$keyArr[1]][$keyArr[2]][$keyArr[3]] =
Hope anyone has an idea on how to solve this!
Try this approach:
$arr = [
"something" => [
'something_else' => [
"another_thing" => "boo"
]
],
"something2" => [
'something_elseghf' => [
"another_thingfg" => [
"hi" => "bye"
]
]
],
"info" => [
'something_else2' => [
"another_thingh" => "boo"
]
],
];
$keyArr = ["something2", 'something_elseghf', "another_thingfg", "hi"];
$cursor = $arr;
foreach ($keyArr as $key) {
$cursor = $cursor[$key];
}
echo $cursor;
Will echo
bye
UPDATE:
If you want to change a value within multi-dimentional array, then use a recursive function, like this:
function changeValue($array, $path, $value) {
if (empty($path)) {
return $value;
}
$key = array_shift($path);
$array[$key] = changeValue($array[$key], $path, $value);
return $array;
}
$arr = [
"something" => [
'something_else' => [
"another_thing" => "boo"
]
],
"something2" => [
'something_elseghf' => [
"another_thingfg" => [
"hi" => "bye"
]
]
],
"info" => [
'something_else2' => [
"another_thingh" => "boo"
]
],
];
$keyArr = ["something2", 'something_elseghf', "another_thingfg", "hi"];
$changedArray = changeValue($arr, $keyArr, 'New value!');
print_r($changedArray);
Will output
Array
(
[something] => Array
(
[something_else] => Array
(
[another_thing] => boo
)
)
[something2] => Array
(
[something_elseghf] => Array
(
[another_thingfg] => Array
(
[hi] => New value!
)
)
)
[info] => Array
(
[something_else2] => Array
(
[another_thingh] => boo
)
)
)

Lower child array keys and combine items

I've one array
$arr = [
'parent' => [
'CHILD' => [
5,6
],
'child' => [
1,2,3,4
],
'Child' => [
5,6,7,8
],
...
]
];
I want to lower the child keys and combine each child having the same case insensitive keys like
$arr = [
'parent' => [
'child' => [
1,2,3,4,5,6,7,8
],
]
];
I've tried with array_change_key_case which always takes the last element and ignores the others.
An array may have multiple children with the same key (with different case)
Try the code below should work:
<?php
$arr = [
'parent' => [
'CHILD' => [
5,6
],
'child' => [
1,2,3,4
],
]
];
$arNew = [];
foreach ($arr as $sParent => $ar) {
foreach ($ar as $sChild => $ar1) {
$sChild = strtolower($sChild);
if (empty($arNew[$sParent][$sChild])) {
$arNew[$sParent][$sChild] = $ar1;
} else {
$arNew[$sParent][$sChild] = array_merge($arNew[$sParent][$sChild], $ar1);
}
}
}
print_r($arNew);

Recursively sort keys of a multidimensional array

I am having a hard time trying recursively sort a multidimensional array on its keys. I tried with usort(), but with no success.
Sample data:
[
'first_level' => [
'dir_3' => [
'subdir_1' => [
'file_2.mp4' => (object) [
'name' => 'file_2.mp4',
],
'file_1.mp4' => (object) [
'name' => 'file_1.mp4',
],
],
],
'dir_1' => [
'subdir_2' => [
'file_6.mp4' => (object) [
'name' => 'file_6.mp4',
],
'file_9.mp4' => (object) [
'name' => 'file_9.mp4',
],
'file_7.mp4' => (object) [
'name' => 'file_7.mp4',
],
],
'subdir_1' => [
'file_8.mp4' => (object) [
'name' => 'file_8.mp4',
],
],
],
],
]
Desired result:
[
'first_level' => [
'dir_1' => [
'subdir_1' => [
'file_8.mp4' => (object) [
'name' => 'file_8.mp4',
],
],
'subdir_2' => [
'file_6.mp4' => (object) [
'name' => 'file_6.mp4',
],
'file_7.mp4' => (object) [
'name' => 'file_7.mp4',
],
'file_9.mp4' => (object) [
'name' => 'file_9.mp4',
],
],
],
'dir_3' => [
'subdir_1' => [
'file_1.mp4' => (object) [
'name' => 'file_1.mp4',
],
'file_2.mp4' => (object) [
'name' => 'file_2.mp4',
],
],
],
],
]
Use a recursive function to call ksort on the current level and all deeper subarrays.
function recur_ksort(&$array) {
foreach ($array as &$value) {
if (is_array($value))
recur_ksort($value);
}
ksort($array);
}
recur_ksort($array);
var_export($array);
Demo: https://3v4l.org/Xede5
You need to use ksort with recursion. Demo
function recursive_ksort(&$array) {
foreach ($array as &$v) {
if (is_array($v)) {
recursive_ksort($v);
}
}
ksort($array);
}
recursive_ksort($array);
var_export($array);
function ksort_recursive(&$array)
{
if (is_array($array)) {
ksort($array);
array_walk($array, 'ksort_recursive');
}
}
It is not necessary within the recursive function to return ksort() -- this would return the unwanted success boolean value from ksort() anyhow.
Note that this function does not throw "Warning: ksort() expects parameter 1 to be array" when given a non-array - this matches my requirements but perhaps not yours. Demo: https://3v4l.org/bogAU
It is fair to assume that you'd like your data ti be sorted "naturally" -- meaning that the number portion of the directory and file names should be sorted numerically instead of as simple strings. Without sorting naturally, dir_10 will be moved in front of dir_2 because when comparing the 5th character of the two strings, 1 is less than 2.
Code: (Demo)
function nat_ksort_r(&$data): void
{
if (is_array($data)) {
ksort($data, SORT_NATURAL);
array_walk($data, __METHOD__);
}
}
nat_ksort_r($array);
var_export($array);
For natural sorting, apply the SORT_NATURAL flag to the ksort() call.
For simpler maintenance of the recursive function, recall the function using the __METHOD__ magic constant. This makes one less place to change nat_ksort_r() if you wish to call the custom function something else.
This recursive function does not return any data; it modifies the original array by reference.
The lowest level contains objects and this data does not get sorted by the function.
The above function can also use a classic loop instead of a functional iterator. (Demo)
function nat_ksort_r(&$data): void
{
if (is_array($data)) {
ksort($data, SORT_NATURAL);
foreach ($data as &$item) {
(__METHOD__)($item);
}
}
}
nat_ksort_r($array);
var_export($array);
You can even write the code in a completely anonymous fashion. Demo
$nat_ksort_r = function(&$data) use (&$nat_ksort_r) {
if (is_array($data)) {
ksort($data, SORT_NATURAL);
foreach ($data as &$item) {
$nat_ksort_r($item);
}
}
};
$nat_ksort_r($array);
var_export($array);

Categories