PHP : multidimensional array merge recursive - php

I need to merge those two arrays:
$ar1 = array("color" => array("red", "green"), "aa");
$ar2 = array("color" => array( "green", "blue"), "bb");
$result = array_merge_recursive($ar1, $ar2);
Expected output:
[
'color' => [
(int) 0 => 'red',
(int) 1 => 'green',
(int) 3 => 'blue'
],
(int) 0 => 'aa',
(int) 1 => 'bb'
]
But it outputs:
[
'color' => [
(int) 0 => 'red',
(int) 1 => 'green',
(int) 2 => 'green', (!)
(int) 3 => 'blue'
],
(int) 0 => 'aa',
(int) 1 => 'bb'
]
I'm looking for the simplest way to do this, my array inputs won't be deeper than those examples.

Here it is.
function array_merge_recursive_ex(array $array1, array $array2)
{
$merged = $array1;
foreach ($array2 as $key => & $value) {
if (is_array($value) && isset($merged[$key]) && is_array($merged[$key])) {
$merged[$key] = array_merge_recursive_ex($merged[$key], $value);
} else if (is_numeric($key)) {
if (!in_array($value, $merged)) {
$merged[] = $value;
}
} else {
$merged[$key] = $value;
}
}
return $merged;
}

Thanks to Meglio comment, you can use these functions for any number of arrays :
Functions
function drupal_array_merge_deep() {
$args = func_get_args();
return drupal_array_merge_deep_array($args);
}
// source : https://api.drupal.org/api/drupal/includes%21bootstrap.inc/function/drupal_array_merge_deep_array/7.x
function drupal_array_merge_deep_array($arrays) {
$result = array();
foreach ($arrays as $array) {
foreach ($array as $key => $value) {
// Renumber integer keys as array_merge_recursive() does. Note that PHP
// automatically converts array keys that are integer strings (e.g., '1')
// to integers.
if (is_integer($key)) {
$result[] = $value;
}
elseif (isset($result[$key]) && is_array($result[$key]) && is_array($value)) {
$result[$key] = drupal_array_merge_deep_array(array(
$result[$key],
$value,
));
}
else {
$result[$key] = $value;
}
}
}
return $result;
}
Usage
$merged = drupal_array_merge_deep($ar_1, $ar_2);
var_dump($merged);
$merged = drupal_array_merge_deep_array([$ar_1, $ar_2]);
var_dump($merged);
Usage (test data)
$ar_1 = [
"item1" => false,
"item2" => true,
"item_list" => [
"sub_item1" => 5,
"sub_itemlist" => [
"sub_sub_item1" => 27,
],
]
];
$ar_2 = [
"item_list" => [
"sub_item2" => 5,
"sub_itemlist" => [
"sub_sub_item2" => 27,
],
],
"item3" => true,
];
Usage output (same for both functions)
array (size=4)
'item1' => boolean false
'item2' => boolean true
'item_list' =>
array (size=3)
'sub_item1' => int 5
'sub_itemlist' =>
array (size=2)
'sub_sub_item1' => int 27
'sub_sub_item2' => int 27
'sub_item2' => int 5
'item3' => boolean true

Related

Filter array with array_walk_recursive but deny specific values

I am trying to filter an array
$array = [
[
'id' => 1,
'some_key' => 'some_value',
'attribute' => [
'id' => 45,
'cat_id' => 1
],
'sub' => [
'id' => 17,
'some_key' => 'some_value',
'attribute' => [
'id' => 47,
'cat_id' => 17
],
],
],
[
'id' => 2,
'some_key' => 'some_value',
'sub' => [
'id' => 19,
'some_key' => 'some_value',
],
]
];
$childArray = [];
array_walk_recursive($array, static function($value, $key) use(&$childArray){
if($key === 'id') {
$childArray[] = $value;
}
});
This returns me an array of all array-fields having id as key.
[1,45,17,47,2,19]
But there is a small problem, some of the array have an key called attribute containing an idkey field that I dont want to have.
[1,17,2,19] //this is what I want
Is there a way to say "don't take the id inside attribute" ?
My current solution, I added a filter before my filter :D
/**
* #param array $array
* #param string $unwanted_key
*/
private function recursive_unset(&$array, $unwanted_key)
{
unset($array[$unwanted_key]);
foreach ($array as &$value) {
if (is_array($value)) {
$this->recursive_unset($value, $unwanted_key);
}
}
}
but this seems like this is not the best practice ^^
You can traverse recursively manually instead of array_walk_recursive and avoid all under attribute key.
<?php
$childArray = [];
function collectIDs($arr,&$childArray){
foreach($arr as $key => $value){
if($key === 'attribute') continue;
if(is_array($value)) collectIDs($value,$childArray);
else if($key === 'id') $childArray[] = $value;
}
}
collectIDs($array,$childArray);
print_r($childArray);
Demo: https://3v4l.org/V6uFf
Find a function that will flatten your array. The result should look like this (I have a class for this):
array (
0 =>
array (
'id' => 1,
'some_key' => "some_value",
'attribute.id' => 45,
'attribute.cat_id' => 1,
'sub.id' => 17,
'sub.some_key' => "some_value",
'sub.attribute.id' => 47,
'sub.attribute.cat_id' => 17,
),
1 =>
array (
'id' => 2,
'some_key' => "some_value",
'sub.id' => 19,
'sub.some_key' => "some_value",
),
)
So you have all keys available and can work with a modified array_walk.
$childArray = [];
array_walk_recursive($data, static function($value, $key) use(&$childArray){
$keys = array_reverse(explode(".",$key));
if($keys[0] === 'id' AND (!isset($keys[1]) OR $keys[1] != 'attribute')) {
$childArray[] = $value;
}
});
The RecursiveArrayIterator class is also very suitable when array values ​​are to be collected depending on keys and values ​​on different levels.
$result = [];
$it = new RecursiveIteratorIterator(new RecursiveArrayIterator($array));
foreach($it as $key => $value) {
$parentLevel = $it->getDepth()-1;
$parentKey = $it->getSubIterator($parentLevel)->key();
if($key === 'id' AND $parentKey !== 'attribute'){
$result[] = $value;
}
}
var_export($result);
//array ( 0 => 1, 1 => 17, 2 => 2, 3 => 19, )
To return the qualifying ids instead of creating a reference variable, merge recursive calls of the function as you iterate.
Code: (Demo)
function getIds($array) {
$ids = [];
foreach ($array as $key => $value) {
if ($key === 'id') {
$ids[] = $value;
} elseif ($key !== 'attribute' && is_array($value)) {
$ids = array_merge($ids, getIds($value));
}
}
return $ids;
}
var_export(getIds($array));
Output:
array (
0 => 1,
1 => 17,
2 => 2,
3 => 19,
)

How do I move an array element with a known key weight of an array in PHP?

Having a brain freeze over a fairly trivial problem. If I start with an array like this:
$my_array = array(
1 => [
"id" => 1,
"weight" => 0
],
2 => [
"id" => 2,
"weight" => -1
],
3 => [
"id" => 3,
"weight" => 0
],
4 => [
"id" => 4,
"weight" => -1
],
);
and i will do a function that move the keys of the array to key + 'weight'. So the result gona be like this:
$my_array = array(
1 => [
"id" => 2,
"weight" => -1
],
2 => [
"id" => 1,
"weight" => 0
],
3 => [
"id" => 4,
"weight" => -1
],
4 => [
"id" => 3,
"weight" => 0
],
);
What is the most efficient way to do this?
Here is the function if it can help some people
function reorder_array(&$my_array) {
foreach ($my_array as $key => $object) {
$move = $object['weight'];
$arr = $my_array;
if ($move == 0 || !isset($arr[$key])) {
continue;
}
$i = 0;
foreach($arr as &$val){
$val = array('sort' => (++$i * 10), 'val' => $val);
}
$arr[$key]['sort'] = $arr[$key]['sort'] + ($move * 10 + ($key == $key ? ($move < 0 ? -5 : 5) : 0));
uasort($arr, function($a, $b) {
return $a['sort'] > $b['sort'];
});
foreach($arr as &$val) {
$val = $val['val'];
}
$my_array = $arr;
}
}
Source of my solution

PHP how to convert an single array to multidimentional array?

I have that single array and I need to convert in a multidimensional array without using array_merge, array_replace_recurcive etc, just an autonomous function:
$single = [
0 => 'one',
1 => 'two',
2 => 'tree',
3 => 'four',
4 => 'five'
];
And convert to look like this, with the last key as value:
$multidimentional = [
'one' => [
'two' => [
'tree' => [
'four' => 'five'
]
]
]
];
I have create a recursion function if this helps:
function array_replace_recursive($defaults, $replaces) {
if(is_null($replaces)) {
$replaces = [];
}
if(!is_array($defaults) || !is_array($replaces)) {
return $replaces;
}
foreach($defaults as $key => $value) {
if(!array_key_exists($key, $replaces) || is_null($replaces[$key])) {
$replaces[$key] = $value;
} else {
if(is_array($replaces[$key]) && is_array($value)) {
$replaces[$key] = array_replace_recursive($replaces[$key], $value);
}
}
}
return $replaces;
}
You can do this with PHP (as of 5.6) argument unpacking and a very simple recursive function:
$single = [
0 => 'one',
1 => 'two',
2 => 'tree',
3 => 'four',
4 => 'five'
];
function convert($key, ...$values) {
if (count($values) == 1)
return array($key => $values[0]);
else
return array($key => convert(...$values));
}
print_r(convert(...$single));
Output:
Array
(
[one] => Array
(
[two] => Array
(
[tree] => Array
(
[four] => five
)
)
)
)
You can also do it without using count (only isset):
function convert2($key, ...$values) {
if (!isset($values[1]))
return array($key => $values[0]);
else
return array($key => convert(...$values));
}
print_r(convert2(...$single));
Output:
Array
(
[one] => Array
(
[two] => Array
(
[tree] => Array
(
[four] => five
)
)
)
)
Thinking in recursion, you can write a base case that returns the value of the currently seen item if it is one less than the length of the array.
$singleDim = [
0 => 'one',
1 => 'two',
2 => 'tree',
3 => 'four',
4 => 'five'
];
function toMultiDimArray($arr, $seen=0) {
if ([] === $arr) {
return [];
}
if(count($arr) - 1 === $seen) {
return $arr[$seen];
}
return [
$arr[$seen] => toMultiDimArray($arr, $seen+1)
];
}
$multiDim = toMultiDimArray($singleDim);
var_dump($multiDim);
array(1) {
["one"]=>
array(1) {
["two"]=>
array(1) {
["tree"]=>
array(1) {
["four"]=>
string(4) "five"
}
}
}
}
$single = [
0 => 'one',
1 => 'two',
2 => 'tree',
3 => 'four',
4 => 'five'
];
function to_multidimentional($array, $count = 0, $seen = 0) {
if($count - 1 === $seen) {
return $array[$seen];
}
return [
$array[$seen] => to_multidimentional($array, $count, $seen + 1)
];
}
$single = to_multidimentional($single, count($single));
print_r($single);
exit;
There's nothing recursive about this. It's just iterating over the $single array backwards and then embedding the result inside the new key each time:
function foo( $single ) {
$multidimensional = [ $single[ count($single) - 1 ] ];
for( $i = count($single) - 2; $i >= 0; $i-- ) {
$multidimensional = [ $single[$i] => $multidimensional ];
}
}

Get all values from multidimensional array

Let's say, I have an array like this:
$array = [
'car' => [
'BMW' => 'blue',
'toyota' => 'gray'
],
'animal' => [
'cat' => 'orange',
'horse' => 'white'
]
];
Then, I want to get all the values (the colour, 'blue', 'gray', 'orange', 'white') and join them into a single array. How do I do that without using foreach twice?
Thanks in advance.
TL;DR
$result = call_user_func_array('array_merge', $array);
Credit: How to "flatten" a multi-dimensional array to simple one in PHP?
In your use case, you should use it like this:
<?php
$array = [
'car' => [
'BMW' => 'blue',
'toyota' => 'gray'
],
'animal' => [
'cat' => 'orange',
'horse' => 'white'
]
];
$result = call_user_func_array('array_merge', $array);
$result = array_values($result);
//$result = ['blue', 'gray', 'orange', 'white']
Old but as far i see not really a "working on all cases" function posted.
So here is the common classic recursively function:
function getArrayValuesRecursively(array $array)
{
$values = [];
foreach ($array as $value) {
if (is_array($value)) {
$values = array_merge($values,
getArrayValuesRecursively($value));
} else {
$values[] = $value;
}
}
return $values;
}
Example array:
$array = [
'car' => [
'BMW' => 'blue',
'toyota' => 'gray'
],
'animal' => [
'cat' => 'orange',
'horse' => 'white'
],
'foo' => [
'bar',
'baz' => [
1,
2 => [
2.1,
'deep' => [
'red'
],
2.2,
],
3,
]
],
];
Call:
echo var_export(getArrayValuesRecursively($array), true) . PHP_EOL;
Result:
// array(
// 0 => 'blue',
// 1 => 'gray',
// 2 => 'orange',
// 3 => 'white',
// 4 => 'bar',
// 5 => 1,
// 6 => 2.1,
// 7 => 'red',
// 8 => 2.2,
// 9 => 3,
// )
Try this:
function get_values($array){
foreach($array as $key => $value){
if(is_array($array[$key])){
print_r (array_values($array[$key]));
}
}
}
get_values($array);
How do I do that without using foreach twice?
First use RecursiveIteratorIterator class to flatten the multidimensional array, and then apply array_values() function to get the desired color values in a single array.
Here are the references:
RecursiveIteratorIterator class
array_values()
So your code should be like this:
$iterator = new RecursiveIteratorIterator(new RecursiveArrayIterator($array));
$flatten_array = array_values(iterator_to_array($iterator,true));
// display $flatten_array
echo "<pre>"; print_r($flatten_array);
Here's the live demo
Here's a recursive function that gives you both the ability to get an array of those endpoint values, or to get an array with all keys intact, but just flattened.
Code:
<?php
$array = [
'car' => [
'BMW' => 'blue',
'toyota' => 'gray'
],
'animal' => [
'cat' => 'orange',
'horse' => 'white'
]
];
//
print "\n<br> Array (Original): ".print_r($array,true);
print "\n<br> Array (Flattened, With Keys): ".print_r(FlattenMultiArray($array,true),true);
print "\n<br> Array (Flattened, No Keys): ".print_r(FlattenMultiArray($array,false),true);
//
function FlattenMultiArray($array,$bKeepKeys=true,$key_prefix='')
{
//
$array_flattened=Array();
//
foreach($array as $key=>$value){
//
if(Is_Array($value)){
$array_flattened=Array_Merge(
$array_flattened,
FlattenMultiArray($value,$bKeepKeys,$key)
);
}
else{
if($bKeepKeys){
$array_flattened["{$key_prefix}_{$key}"]=$value;
}
else{
$array_flattened[]=$value;
}
}
}
return $array_flattened;
}
?>
Outputs:
<br> Array (Original): Array
(
[car] => Array
(
[BMW] => blue
[toyota] => gray
)
[animal] => Array
(
[cat] => orange
[horse] => white
)
)
<br> Array (Flattened, With Keys): Array
(
[car_BMW] => blue
[car_toyota] => gray
[animal_cat] => orange
[animal_horse] => white
)
<br> Array (Flattened, No Keys): Array
(
[0] => blue
[1] => gray
[2] => orange
[3] => white
)
If you don't care about the indexes, then this should do it:
$colors = array();
foreach ($array as $item) {
$colors = array_merge($colors, array_values($item));
}
If you want to keep the indexes you could use:
$colors = array();
foreach ($array as $item) {
// this leaves you open to elements overwriting each other depending on their keys
$colors = array_merge($colors, $item);
}
I hope this helps.
what about this one?It works with ANY array depth.
private function flattenMultiArray($array)
{
$result = [];
self::flattenKeyRecursively($array, $result, '');
return $result;
}
private static function flattenKeyRecursively($array, &$result, $parentKey)
{
foreach ($array as $key => $value) {
$itemKey = ($parentKey ? $parentKey . '.' : '') . $key;
if (is_array($value)) {
self::flattenKeyRecursively($value, $result, $itemKey);
} else {
$result[$itemKey] = $value;
}
}
}
P.S. solution is not mine, but works perfectly, and I hope it will help someone.
Original source:
https://github.com/letsdrink/ouzo/blob/master/src/Ouzo/Goodies/Utilities/Arrays.php

PHP Question: how to array_intersect_assoc() recursively

Let's say I want to do this:
$a = array_intersect_assoc(
array(
'key1' => array(
'key2' => 'value2'
),
'key3' => 'value3',
'key4' => 'value4'
),
array(
'key1' => array(
'key2' => 'some value not in the first parameter'
),
'key3' => 'another value'
)
);
var_dump( $a );
The printed result is:
array
'key1' =>
array
'key2' => string 'value2' (length=6)
It's clear that values associated with 'key2' in both arrays are not the same, however array_intersect_assoc() still return 'key2' => 'value2' as the intersected value.
Is this the expected behavior of array_intersect_assoc()?
Thanks!
Yes, it's the expected behavior, because the comparison is done using string representations, and the function does not recurse down nested arrays. From the manual:
The two values from the key => value pairs are considered equal only if (string) $elem1 === (string) $elem2 . In other words a strict type check is executed so the string representation must be the same.
If you tried to intersect with an array with 'key1' => 'Array', you'd get the same result because the string representation of an array is always 'Array'.
One of the user-contributed notes, by nleippe, contains a recursive implementation that looks promising (I modified the third line to do string comparison on any non-array values):
function array_intersect_assoc_recursive(&$arr1, &$arr2) {
if (!is_array($arr1) || !is_array($arr2)) {
// return $arr1 == $arr2; // Original line
return (string) $arr1 == (string) $arr2;
}
$commonkeys = array_intersect(array_keys($arr1), array_keys($arr2));
$ret = array();
foreach ($commonkeys as $key) {
$ret[$key] =& array_intersect_assoc_recursive($arr1[$key], $arr2[$key]);
}
return $ret;
}
function array_key_match_recursive(array $main, array $other, $i = 0, &$result = []) {
foreach($main as $key => $value) {
$k = sprintf('%s%s', str_repeat('=', $i), $key);
if (!isset($other[$key])) {
$result[$k][] = 'not key';
}
if (!is_array($value) && empty($other[$key])) {
$result[$k][] = 'value empty';
}
if (is_array($value) && isset($other[$key])) {
array_key_match_recursive($value, $other[$key], ++$i, $result);
}
}
//return (bool) !$result;
return $result;
}
A function that does what you need:
/**
* Get array intersect assoc recursive.
*
* #param mixed $value1
* #param mixed $value2
*
* #return array|bool
*/
function getArrayIntersectAssocRecursive(&$value1, &$value2)
{
if (!is_array($value1) || !is_array($value1)) {
return $value1 === $value2;
}
$intersectKeys = array_intersect(array_keys($value1), array_keys($value2));
$intersectValues = [];
foreach ($intersectKeys as $key) {
if (getArrayIntersectAssocRecursive($value1[$key], $value2[$key])) {
$intersectValues[$key] = $value1[$key];
}
}
return $intersectValues;
}
My version based on #BoltClock version:
function array_intersect_assoc_recursive($arr1, $arr2) {
if (!is_array($arr1) || !is_array($arr2)) {
return $arr1;
}
$commonkeys = array_keys($arr1);
if (!array_key_exists('$', $arr2)){
$commonkeys = array_intersect(array_keys($arr1), array_keys($arr2));
}
$ret = array();
foreach ($commonkeys as $key) {
$ret[$key] = array_intersect_assoc_recursive($arr1[$key], array_key_exists('$', $arr2) ? $arr2['$'] : $arr2[$key]);
}
return $ret;
}
I use this code to filter data inside complex array
example:
$filter = [
'channels' => [
'$' => [
'id' => 1,
'type' => 1,
'count' => 1
]
],
'user' => [
'id' => 1,
'type' => 1
]
];
$data = [
'user' => [
'id' => '1234',
'type' => true,
'counter' => 14,
],
'filteredField' => 4,
'channels' => [
['id' => '567', 'type' => 'other', 'count' => 1345, 'filteredField' => 5,],
['id' => '890', 'type' => 'other', 'count' => 5456, 'filteredField' => 7,],
],
];
print_r(array_intersect_assoc_recursive($data, $filter));
test online:
https://onlinephp.io/c/3be04

Categories