Merge 2 arrays in PHP, keeping only keys from first array - php

I have these two arrays:
$list = [
'fruit' => [],
'animals' => [],
'objects' => [],
];
$dataArray = [
'fruit' => 'apple',
'animals' => ['dog', 'cat'],
'asd' => 'bla'
];
I want to merge them so that $list at the end is:
[fruit] => Array
(
[0] => apple
)
[animals] => Array
(
[0] => dog
[1] => cat
)
[objects] => Array
(
)
so, things to pay attention to:
even if 'fruit' had only one element, is still an array in $list
keys missing from $list ('asd' key) are simply ignored
keys with no values are still kept, even if empty
Using array_merge doesn't work:
$merged = array_merge($list, $dataArray);
[fruit] => apple
[animals] => Array
(
[0] => dog
[1] => cat
)
[objects] => Array
(
)
[asd] => bla
I managed to get what I want with this:
foreach ($dataArray as $key => $value) {
if (isset($list[$key])) {
if (is_array($value)) {
$list[$key] = $value;
}
else {
$list[$key] = [$value];
}
}
}
But I was wondering if there was a cleaner way to do it or some other php function that I'm not aware of.

You can achieve this in a couple of steps:
$result = array_intersect_key($dataArray, $list);
This will return you an array containing all the elements that are in the first array, and then filter against the keys in the second. This gives you:
Array
(
[fruit] => apple
[animals] => Array
(
[0] => dog
[1] => cat
)
)
You can then re-add the missing keys from the first array, using:
$result + $list;
The difference between the + operator and array_merge is that the former does not overwrite the first array using the values from the second. It will just add any missing elements.
In one line, this works out as:
$result = array_intersect_key($dataArray, $list) + $list;
You can see it in action here: https://eval.in/865733
EDIT: Sorry, completely missed the part about keeping the elements as arrays. If they'll always be arrays, then you can add a quick one-liner like:
$result = array_map(function ($e) { return (array) $e; }, $result);
Which will cast all top-level elements to an array. If your logic is more complex than that, then I'm not sure you'll find a way to do it with in-built functions, sorry.

The second rule ("2. keys missing from $list ('asd' key) are simply ignored") tells me to iterate over $list, not over $dataArray. If $dataArray is much bigger than $list, iterating over it is a waste of time as most of its elements are simply ignored.
Your rules do not explain how to process the elements of $list when they are not empty (I'll assume they are always arrays, otherwise the game changes and it becomes too complicated to provide generic code to handle it).
The code I suggest looks like this:
// Build the result in $result
// (you can modify $list as well if you prefer it that way)
$result = array();
// 2. keys missing from $list ('asd' key) are simply ignored
// iterate over the keys of $list, handle only the keys present in $dataArray
foreach ($list as $key => $value) {
if (array_key_exists($dataArray, $key)) {
// $key is present in $dataArray, use the value from $dataArray
$value = $dataArray[$key];
// 1. even if 'fruit' had only one element, is still an array in $list
if (! is_array($value)) {
$value = array($value);
}
}
// 3. keys with no values are still kept, even if empty
// if the key is not present in $dataArray, its value from $list is used
$result[$key] = $value;
}
If you move the if (! is_array($value)) block outside the if (array_key_exists()) block it will convert to arrays also the values of $list that are not arrays and are associated with keys that are not present in $dataArray (e.g $list['objects']). This way, after the code runs, all values of $result are arrays.
Your code is good as well. Apart from iterating over $list and not over $dataArray, there is no way to make it faster or easier to read in a spectacular manner. The code I suggest here is just another way to write the same thing.

Here is two lines solution ( but you can simply make it one line:) )
$list = [
'fruit' => [],
'animals' => [],
'objects' => [],
];
$dataArray = [
'fruit' => 'apple',
'animals' => ['dog', 'cat'],
'asd' => 'bla'
];
$list = array_merge($list, array_intersect_key($dataArray, $list));
array_walk($list, function(&$item){$item = (array)$item;});

My approach will do 3 very simple operations:
Run a single loop while modifying $list by reference using &$v.
Ignore iterations where a matching key is not found in $dataArray.
Hard-cast each qualifying value from $dataArray as an array and overwrite the initial empty subarray.
If the qualifying value is not an array, it will become the lone element in the generated subarray; if it is, then nothing about the subarray is changed.
Code: (Demo)
$list = [
'fruit' => [],
'animals' => [],
'objects' => []
];
$dataArray = [
'fruit' => 'apple',
'animals' => ['dog', 'cat'],
'asd' => 'bla'
];
foreach ($list as $k => &$v) {
if (isset($dataArray[$k])) {
$v = (array)$dataArray[$k];
}
}
var_export($list);
Output:
array (
'fruit' =>
array (
0 => 'apple',
),
'animals' =>
array (
0 => 'dog',
1 => 'cat',
),
'objects' =>
array (
),
)

Related

Convert Decimal-Number/MIB-Style Strings to Array Indices

I'm trying to convert mib-style strings into PHP array indices. The trick is that I have to do this for a variable number of strings. As an example:
$strings = ['1.1.1' => 1, '1.1.2' => 2, '1.2.1' => 1];
# Given the above, generate the below:
$array = [ 1 => [ 1 => [1 => 1, 2 => 2] ], 2 => [1 => 1] ] ] ] ]
I can't think of a way to do it that isn't just a brute-force, inefficient method. Any helpful function/approach/advice is welcome.
You could take a recursive approach since the problem/result you provide seems to be of a recursive nature. (You can achieve the same result with a loop, applying the same logic as the recursive function ofcourse)
So under the assumption that there are no conflicting string inputs/edge cases what so ever, the following could be one approach:
Loop over all the strings and their values, break it up and create a nested structure by passing the result array by its reference.
function createNested($pieces, $currentIndex, &$previous, $value)
{
$index = $pieces[$currentIndex];
// Our base case: when we reached the final/deepest level of nesting.
// Hence when the we reached the final index.
if ($currentIndex == count($pieces) - 1) {
// Can now safely assign the value to index.
$previous[$index] = $value;
} else {
// Have to make sure we do not override the key/index.
if (!key_exists($index, $previous)) {
$previous[$index] = [];
}
// If the key already existed we can just make a new recursive call (note one level deeper as we pass the array that $previous[$index] points to.
createNested($pieces, $currentIndex + 1, $previous[$index], $value);
}
}
$strings = ['1.1.1' => 1, '1.1.2' => 2, '1.2.1' => 1];
$result = [];
foreach ($strings as $string => $value) {
// Break up the string by .
createNested(explode('.', $string), 0, $result, $value);
}
echo '<pre>';
print_r($result);
echo '</pre>';
Will output:
Array
(
[1] => Array
(
[1] => Array
(
[1] => 1
[2] => 2
)
[2] => Array
(
[1] => 1
)
)
)

Flatten array joining keys with values

I have this multidimensional array:
$array = array(
'user1' => array('Miguel'),
'user2' => array('Miguel', 'Borges', 'João'),
'user3' => array(
'Sara',
'Tiago' => array('Male')
)
);
I want it flatten, transformed into:
$new_array = array(
'user1.Miguel',
'user2.Miguel',
'user2.Borges',
'user2.João',
'user3.Sara',
'user3.Tiago.Male',
);
Important:
The keys are very important to me. I want them concatenated,
separated by periods.
It should work with any level of nesting.
Thank you!
Though not explicitly stated in your question, it seems that you need to concatenate the string keys and ignore the integer keys (which may be easily achieved with is_string($key)). And since you need your code to “work with any level of nesting,” a recursive function would serve your purpose best:
function array_flatten_key($arr){
$_arr = array();
foreach($arr as $key => $val){
if(is_array($val)){
foreach(array_flatten_key($val) as $_val){
$_arr[] = is_string($key) ? $key.".".$_val : $_val;
}
}
else{
$_arr[] = is_string($key) ? $key.".".$val : $val;
}
}
return $_arr;
}
$new_array = array_flatten_key($array);
print_r($new_array);
The output will be:
Array
(
[0] => user1.Miguel
[1] => user2.Miguel
[2] => user2.Borges
[3] => user2.João
[4] => user3.Sara
[5] => user3.Tiago.Male
)

php create multidimensional array from flat one

I have an array like this:
<?php
$array = array( 0 => 'foo', 1 => 'bar', ..., x => 'foobar' );
?>
What is the fastest way to create a multidimensional array out of this, where every value is another level?
So I get:
array (size=1)
'foo' =>
array (size=1)
'bar' =>
...
array (size=1)
'x' =>
array (size=1)
0 => string 'foobar' (length=6)
<?php
$i = count($array)-1;
$lasta = array($array[$i]);
$i--;
while ($i>=0)
{
$a = array();
$a[$array[$i]] = $lasta;
$lasta = $a;
$i--;
}
?>
$a is the output.
What exactly are you trying to do? So many arrays of size 1 seems a bit silly.
you probably want to use foreach loop(s) with a key=>value pair
foreach ($array as $k=>$v) {
print "key: $k value: $v";
}
You could do something like this to achieve the array you asked for:
$newArray = array();
for ($i=count($array)-1; $i>=0; $i--) {
$newArray = array($newArray[$i]=>$newArray);
}
I'm confused about what you want to do with non-numeric keys (ie, x in your example). But in any case using array references will help
$array = array( 0 => 'foo', 1 => 'bar', x => 'foobar' );
$out = array();
$curr = &$out;
foreach ($array as $key => $value) {
$curr[$value] = array();
$curr = &$curr[$value];
}
print( "In: \n" );
print_r($array);
print( "Out : \n" );
print_r($out);
Prints out
In:
Array
(
[0] => foo
[1] => bar
[x] => foobar
)
Out :
Array
(
[foo] => Array
(
[bar] => Array
(
[foobar] => Array
(
)
)
)
)
You can use a recursive function so that you're not iterating through the array each step. Here's such a function I wrote.
function expand_arr($arr)
{
if (empty($arr))
return array();
return array(
array_shift($arr) => expand_arr($arr)
);
}
Your question is a little unclear since in your initial statement you're using the next value in the array as the next step down's key and then at the end of your example you're using the original key as the only key in the next step's key.

Multidimenssion array compare two arrays and update first array value

I want to filter a array by a number and update its status in the first array.
I have two array $arr1,$arr2
$arr1 = array(
0=>array('number'=>100,name=>'john'),
1=>array('number'=>200,name=>'johnny')
);
$arr2= array(
0=>array('number'=>300,name=>'r'),
1=>array('number'=>100,name=>'b'),
2=>array('number'=>200,name=>'c')
);
Final output should be an array like this
$arr1 = array(
0=>array('number'=>100,name=>'b'),
1=>array('number'=>200,name=>'c')
);
Any ideas to start off please ?
For specialized array modifications like this, the method of choice is array walk. It allows you to apply a custom function to each element in a given array.
Now, because of your data format, you will have to do a loop. Wrikken is asking if you can retrieve or transform the data to provide faster access. The algorithm below is O(n^2): it will require as many cycles as there are elements in the first array times the number of elements in the second array, or exactly count($arr1) * count($arr2).
function updateNameFromArray($element, $key, $arr2) {
foreach($arr2 as $value) {
if($value['number'] == $element['number']) {
$element['name'] == $value['name'];
break;
}
}
}
array_walk($arr1, "updateNameFromArray", $arr2);
Now, what Wrikken is suggesting is that if your arrays can be changed to be keyed on the 'number' property instead, then the search/replace operation is much easier. So if this were your data instead:
$arr1 = array(
100=>array('number'=>100,name=>'john'),
200=>array('number'=>200,name=>'johnny')
);
// notice the keys are 100 and 200 instead of 0,1
$arr2= array(
300=>array('number'=>300,name=>'r'),
100=>array('number'=>100,name=>'b'),
200=>array('number'=>200,name=>'c')
);
// notice the keys are 300, 100 and 200 instead of 0,1, 2
Then you could do this in O(n) time, with only looping over the first array.
foreach($arr1 as $key => $value) {
if(isset($arr2[$key])) {
$value['number'] = $arr2[$key]['number'];
}
}
Try this. It's not that clean but i think it would work.
<?php
$arr1 = array(0=>array('number'=>100,'name'=>'john'),1=>array('number'=>200,'name'=>'johnny'));
$arr2= array(0=>array('number'=>300,'name'=>'r'),1=>array('number'=>100,'name'=>'b'),2=>array('number'=>200,'name'=>'c'));
foreach( $arr1 as $key=>$data1 )
{
foreach( $arr2 as $key2=>$data2 )
{
if( $data1['number'] == $data2['number'] )
{
$arr1[$key]['name'] = $arr2[$key2]['name'];
}
}
}
print_r( $arr1 );
?>
the output would be :
Array
(
[0] => Array
(
[number] => 100
[name] => b
)
[1] => Array
(
[number] => 200
[name] => c
)
)
There isn't really a simple way for this to be accomplished with generic PHP functions, so, You might need to create mapping arrays.
The way I would approach this, is creating a loop that goes through the first array, and maps the number value as a key to the index of it's place in $arr1 giving you:
$tmp1 = array();
foreach ($arr1 as $key => $number_name) {
$tmp1[$number_name['number']] = $key;
}
This should give you an array that looks like
$tmp1 [
100 => 0,
200 => 1
];
Then I would loop through the second array, get the number value, if that existed as a key in $tmp1, get the associated value (being the key for $arr1), and use that to update the name in $arr1.
// Loop through $arr2
foreach ($arr2 as $number_name) {
// Get the number value
$number = $number_name['number'];
// Find the $arr1 index
if (isset($tmp1[$number])) {
$arr1_key = $tmp1[$number];
// Set the $arr1 name value
$arr1[$arr1_key]['name'] = $number_name['name'];
}
}
<?php
//Set the arrays
$arr1 = array(
array('number'=>100,'name'=>'john'),
array('number'=>200,'name'=>'johnny')
);
$arr2= array(
array('number'=>300,'name'=>'r'),
array('number'=>100,'name'=>'b'),
array('number'=>200,'name'=>'c')
);
// use a nested for loop to iterate and compare both arrays
for ($i=0;$i<count($arr1);$i++):
for ($j=0;$j<count($arr2);$j++):
if ($arr2[$j]['number']==$arr1[$i]['number'])
$arr1[$i]['name']=$arr2[$j]['name'];
endfor;
endfor;
print_r($arr1);
OUTPUT:
Array (
[0] => Array ( [number] => 100 [name] => b )
[1] => Array ( [number] => 200 [name] => c )
)
That being said, you should probably reconsider the very way your data is structured. Do you really need a multi-dimensional array or can you use a simple associative array, like so:
// set the arrays
$arr1 = array(
'john'=>100,
'johnny'=>200
);
$arr2 = array(
'r'=>300,
'b'=>100,
'c'=>200
);
// find values in arr2 common to both arrays
$arr3 = array_intersect($arr2, $arr1);
// change the key of arr1 to match the corresponding key in arr2
foreach ($arr3 as $key=>$value) {
$old_key = array_search($value, $arr1);
$arr1[$key]=$arr1[$old_key];
unset($arr1[$old_key]);
}
print_r($arr1);
OUTPUT:
Array (
[b] => 100
[c] => 200
)

Break array into chunks and prepend each row with a static element

I have the following array:
$list = array('item1','item2','item3','item4','item5','item6');
i need to take this array and break it into smaller arrays within 1 array for the csv. Each small array needs to have '999999' at index 0, followed by the next two items in the $list array. So the final result would be like this:
$newList = array(
array(999999, "item1" , "item2"),
array(999999, "item3" , "item4"),
array(999999, "item5" , "item6")
);
The original list array may contain up to 100 values at sometimes. What is the best way to achieve this?
Here's a different way of doing it, please see the comment as to where you place your additional elements to be prepended (you could add a second array into the merge at the end to add elements after the 2 list items too!)
See here for working example: http://codepad.org/hucpT5Yo
<?php
$list = array('item1','item2','item3','item4','item5','item6');
$list_chunks = array_chunk($list,2);
$list_chunks2 = array_map(
create_function(
'$x',
'return array_merge(array(
/* This array is prepended to the second,
add as many elements as you like */
999999,
"another element to add",
483274832
),
$x);'),
$list_chunks);
print_r($list_chunks2);
?>
$idx = 0;
$newarray = array();
$temparray = array('999999');
foreach($oldarray as $value) {
$temparray[] = $value;
if ((++$idx % 2)) == 0) {
$newarray[] = $temparray;
$temparray = array('999999');
}
}
Break your flat array into an array 2-element rows with array_chunk(), then call array_merge() on each row with the static value 999999 nested in an array and nominated as the first argument.
The modern syntax for the chunk-n-merge technique demonstrated by #Lee looks like this:
Code: (Demo)
$list = ['item1','item2','item3','item4','item5','item6'];
var_export(
array_map(fn($row) => array_merge([999999], $row), array_chunk($list, 2))
);
Output:
array (
0 =>
array (
0 => 999999,
1 => 'item1',
2 => 'item2',
),
1 =>
array (
0 => 999999,
1 => 'item3',
2 => 'item4',
),
2 =>
array (
0 => 999999,
1 => 'item5',
2 => 'item6',
),
)

Categories