PHP Compare and change certain elements in multidimensional arrays - php

To be short, I have two simple arrays and I want to verify if certain keys from the second array have empty values and replace them with their correspondent values from the first array.
Example:
$a1 = [ 1 => 'one', 2 => 'two', 3 => 'three',5=>'cinco', 6=>'six'];
$a2 = [ 2 => 'two', 5=>'five', 6=>'' ];
Result:
Array
(
[2] => two
[5] => five
[6] => six
)
The following code works already for this.
$keys = array_keys($a1);
foreach ($keys as $k)
{
if ((isset($a2[$k])) && (($a2[$k]) == '')) {
$a2[$k] = $a1[$k];
}
}
print_r($a2);
But what if we want to apply this for two 2D arrays? What will be the proper approach in that case? Let's say these two 2D arrays will be:
$superheroes_complete = array(
"spiderman" => array(
"name" => "Peter Parker",
"email" => "peterparker#mail.com",
),
"superman" => array(
"name" => "Clark Kent",
"email" => "clarkkent#mail.com",
),
"ironman" => array(
"name" => "Harry Potter",
"email" => "harrypotter#mail.com",
)
);
$superheroes_empty = array(
"spiderman" => array(
"name" => "Peter Parker",
"email" => "",
),
"superman" => array(
"name" => "Clark Kent",
"email" => "something",
),
"ironman" => array(
"name" => "Harry Potter",
"email" => "another one",
)
);
Expectation:
$superheroes = array(
"spider-man" => array(
"name" => "Peter Parker",
"email" => "peterparker#mail.com",
),
"super-man" => array(
"name" => "Clark Kent",
"email" => "something",
),
"iron-man" => array(
"name" => "Harry Potter",
"email" => "another one",
)
);
Much appreciation and thank you in advance!

You've added another level to your data, so you can just add another level to your checking as well with a second foreach loop:
foreach ($superheroes_complete as $hero => $info) {
foreach ($info as $key => $value) {
if (empty($superheroes_empty[$hero][$key])) {
$superheroes_empty[$hero][$key] = $value;
}
}
}

First note that your 1D case can be simplified:
foreach ($a2 as $k => $v) {
if (!isset($v)) {
$a2[$k] = $a1[$k];
}
}
Then for the 2D case, assuming the 1st level keys are always the same (or it becomes a quite different question!):
foreach ($superheroes_complete as $main_k => $main_v) {
foreach ($main_v as $k => $v) {
if (!isset($v)) {
$superheroes_empty[$main_k][$k] = $superheroes_complete[$main_k][$k];
}
}

If you only need to take care of the "email" field you can do this :
<?php
$keys = array_keys($superheroes_complete);
foreach ($keys as $k)
{
if ((isset($superheroes_empty[$k]["email"])) &&
(($superheroes_empty[$k]["email"]) == '')) {
$superheroes_empty[$k]["email"] = $superheroes_complete[$k]["email"];
}
}
var_dump($superheroes_empty);
?>

For the generic case where the depth of nesting is unlimited, you could use this recursive function:
function fillEmpty(&$filled, &$empty) { // arguments are by reference
if ($empty === "") {
$empty = $filled;
return;
}
if (!is_array($filled) || !is_array($empty)) return;
foreach ($filled as $key => $value) {
if (isset($empty[$key])) fillEmpty($value, $empty[$key]);
}
}
Example call:
fillEmpty($superheroes_complete, $superheroes_empty);
This modifies the second argument, filling the empty values.
See it run on eval.in

It might be your lucky day, php has some built in functions to compare arrays values and keys. Use array_diff() which can compare two or more arrays and return the difference. You could also use array_intersect() which does the opposite.
If you want to only compare the keys, use array_diff_key()which returns only the key difference or array_intersect_key() which returns the matched keys.

You could also consider a recursive solution. This could work on both the 1D and 2D arrays, or even an array of N dimensions.
I'm aware that recursion should be used with care, as it can be quite resource intensive. This is however a more versatile solution, and keeps the code cleaner with less nested control structures and early returns, which I find better readable. I'll use the native array_walk method because I was taught that it should perform better then a php loop.
So this is what my code would look like:
function array_complement_recursive(&$partial, $complete) {
array_walk($partial, function(&$value, $key) use ($complete) {
// (for safety) complete does not contain matching key: done
if (! array_key_exists($key, $complete)) {
return;
}
// value is array: call recursive
if (is_array($value)) {
array_complement_recursive($value, $complete[$key]);
return;
}
// value is not 'empty': done
// note that null, 0, false will also not match, you may want to make
// this check more specific to match only the empty string
if ($value) {
return;
}
$value = $complete[$key];
});
}
I've set up a little demo so you can see that it works on both your examples. And as I said, it should even work for arrays with more dimensions, or a more irregular structure.
http://phpfiddle.org/main/code/49iz-vrwg
I've added some comments to explain, but feel free to ask if anything is unclear.

Related

PHP: Unset All Empty Key Value Pairs of an Array

I have an associative array in my PHP code. I want to unset all the keys that have empty value.
I know individual keys can be unset using the following code
unset($array_name['key_name']);
What I don't know is how to recursively do this with an array. While searching for an answer I came across answers recommending PHP built-in function array_filter(), however I failed to grasp how that works. So here's the code I tried to write but it doesn't work either.
<?php
function unset_all_empty_array_keys($associative_array){
foreach ($associative_array as $key => $value){
if ($value == ""){
unset($associative_array['$key']); // This doesn't work.
}
}
};
$list = array(
"item1" => "one",
"item2" => "",
"item3" => "three",
);
unset_all_empty_array_keys($list, '$list');
print_r($list);
echo "<br>";
// Manually Unsetting key-value pair works fine.
unset($list['item2']);
print_r($list);
?>
When you want do it with 'foreach()' then by reference:
function unset_all_empty_array_keys(&$associative_array){
foreach ($associative_array as $key => &$value){
if ($value == ""){
unset($associative_array[$key]);
}
}
}
The way with 'array_filter()':
$list = array_filter($list,function($a){
return $a!='';
})
And can only work 'recursively' on multi-dimensional array
$a = ['a'=>['b'=>'']];
Your example is an flat array with only one dimension.
So here the version for multi-dimensional arrays:
$list = array(
"item1" => "one",
"item2" => "",
"item3" => array(
"item1" => "one",
"item2" => "",
"item3" => "three",
)
);
function unset_all_empty_array_keys_recursive(&$associative_array){
foreach ($associative_array as $key => &$value){
if ($value == ""){
unset($associative_array[$key]);
} else if (is_array($value)){
unset_all_empty_array_keys_recursive($value);
}
}
}
unset_all_empty_array_keys($list);
var_export($list);

Get all the keys of an array and in case the key has no value, return the value

I've just come across a problem, which apparently looks simple, but I can't find a solution. I have the following array in PHP:
[
"name" => "user"
0 => "created_at"
"email" => "mail"
]
And I need to get an array like this:
[
"name"
"created_at"
"email"
]
As you can see, I only need to obtain the original keys from the array, but in case a value from the original array does not have an associated value, then return the value instead of the key.
I have tried several ways using the following methods:
array_flip()
array_keys()
array_values()
I would appreciate in advance anyone who could help me.
Loop through the array and add the desired values to your new array:
$a = [
"name" => "user",
0 => "created_at",
"email" => "mail",
];
$b = [];
foreach ( $a as $k => $v ) {
$b[] = ( $k ? $k : $v );
}
print_r($b);
/*
Array
(
[0] => name
[1] => created_at
[2] => email
)
*/
For a more sophisticated determination of which key(s) to keep, replace the first $k in the ternary expression with a function that tests $k for validity:
$b[] = ( test($k) ? $k : $v );
// ...
function test($k) {
return ! is_number($k); // For example
}
is just a simple foreach
<?php
$test=
[
"name" => "user"
,0 => "created_at"
,"email" => "mail"
];
$res=[];
foreach(array_keys($test) as $ch){
(is_numeric($ch) and ($res[]=$test[$ch]) )or ($res[]=$ch);
}
var_dump($res);
?>

Expand "dot notation" keys in a nested array to child arrays

I start with a nested array of some arbitrary depth. Within that array, some keys are a series of tokens separated by dots. For example "billingAddress.street" or "foo.bar.baz". I would like to expand those keyed elements to arrays, so the result is a nested array with all those keys expanded.
For example:
[
'billingAddress.street' => 'My Street',
'foo.bar.baz' => 'biz',
]
should be expanded to:
[
'billingAddress' => [
'street' => 'My Street',
],
'foo' => [
'bar' => [
'baz' => 'biz',
]
]
]
The original "billingAddress.street" can be left alongside the new "billingAddress" array, but it does not need to be (so the solution may operate on the original array or create a new array). Other elements such as "billingAddress.city" may need to be added to the same expanded portion of the array.
Some keys may have more than two tokens separated by dots, so will need to be expanded deeper.
I've looked at array_walk_recursive() but that only operates on elements. For each matching element key, I actually want to modify the parent array those elements are in.
I've looked at array_map, but that does not provide access to the keys, and as far as I know is not recursive.
An example array to expand:
[
'name' => 'Name',
'address.city' => 'City',
'address.street' => 'Street',
'card' => [
'type' => 'visa',
'details.last4' => '1234',
],
]
This is to be expanded to:
[
'name' => 'Name',
'address.city' => 'City', // Optional
'address' => [
'city' => 'City',
'street' => 'Street',
],
'address.street' => 'Street', // Optional
'card' => [
'type' => 'visa',
'details.last4' => '1234', // Optional
'details' => [
'last4' => '1234',
],
],
]
What I think I need, is something that walks to each array in the nested array and can apply a user function to it. But I do suspect I'm missing something obvious. The payment gateway I am working with sends me this mix of arrays and "pretend arrays" using the dot-notation, and my objective is to normalize it into an array for extracting portions.
I believe the problem differs from similar questions on SO due to this mix of arrays and non-arrays for expanding. Conceptually it is a nested array where sound groups of elements at any level need to be replaced with new arrays, so there are two levels of recursion happening here: the tree walking, and the expansion, and then the walking of the expanded trees to see if there is more expansion needed.
You could find it useful to reverse the order of the keys you get from exploding the combined (dotted) key. In that reversed order it is easier to progressively wrap a previous result into a new array, thereby creating the nested result for one dotted key/value pair.
Finally, that partial result can be merged into the accumulated "grand" result with the built-in array_merge_recursive function:
function expandKeys($arr) {
$result = [];
foreach($arr as $key => $value) {
if (is_array($value)) $value = expandKeys($value);
foreach(array_reverse(explode(".", $key)) as $key) $value = [$key => $value];
$result = array_merge_recursive($result, $value);
}
return $result;
}
See it run on repl.it
Here's a recursive attempt. Note that this doesn't delete old keys, doesn't maintain any key ordering and ignores keys of the type foo.bar.baz.
function expand(&$data) {
if (is_array($data)) {
foreach ($data as $k => $v) {
$e = explode(".", $k);
if (count($e) == 2) {
[$a, $b] = $e;
$data[$a][$b]= $v;
}
expand($data[$k]);
}
}
}
Result:
Array
(
[name] => Name
[address.city] => City
[address.street] => Street
[card] => Array
(
[type] => visa
[details.last4] => 1234
[details] => Array
(
[last4] => 1234
)
)
[address] => Array
(
[city] => City
[street] => Street
)
)
Explanation:
On any call of the function, if the parameter is an array, iterate through the keys and values looking for keys with a . in them. For any such keys, expand them out. Recursively call this function on all keys in the array.
Full version:
Here's a full version that supports multiple .s and cleans up keys afterwards:
function expand(&$data) {
if (is_array($data)) {
foreach ($data as $k => $v) {
$e = explode(".", $k);
$a = array_shift($e);
if (count($e) == 1) {
$data[$a][$e[0]] = $v;
}
else if (count($e) > 1) {
$data[$a][implode(".", $e)] = $v;
}
}
foreach ($data as $k => $v) {
expand($data[$k]);
if (preg_match('/\./', $k)) {
unset($data[$k]);
}
}
}
}
Another solution by #trincot has been accepted as being more elegant, and is the solution I am using now.
Here is my solution, which expands on the solution and tips given by #ggorlen
The approach I have taken is:
Create a new array rather than operate on the initial array.
No need to keep the old pre-expanded elements. They can be added easily if needed.
Expanding the keys is done one level at a time, from the root array, with the remaining expansions passed back in recursively.
The class method:
protected function expandKeys($arr)
{
$result = [];
while (count($arr)) {
// Shift the first element off the array - both key and value.
// We are treating this like a stack of elements to work through,
// and some new elements may be added to the stack as we go.
$value = reset($arr);
$key = key($arr);
unset($arr[$key]);
if (strpos($key, '.') !== false) {
list($base, $ext) = explode('.', $key, 2);
if (! array_key_exists($base, $arr)) {
// This will be another array element on the end of the
// arr stack, to recurse into.
$arr[$base] = [];
}
// Add the value nested one level in.
// Value at $arr['bar.baz.biz'] is now at $arr['bar']['baz.biz']
// We may also add to this element before we get to processing it,
// for example $arr['bar.baz.bam']
$arr[$base][$ext] = $value;
} elseif (is_array($value)) {
// We already have an array value, so give the value
// the same treatment in case any keys need expanding further.
$result[$key] = $this->expandKeys($value);
} else {
// A scalar value with no expandable key.
$result[$key] = $value;
}
}
return $result;
}
$result = $this->expandKeys($sourceArray)

Fastes PHP Multidimensional Array matching

If I have two arrays, one which is a one dimensional array, the second is a undefined depth Multidimensional Array.
I would like to execute a function on the fields from both if the key matches and its not an iterable instance.
The first array is something like this
$array = array(
"st" => "1",
"gr" => "2",
);
And the second...
$array = array(
"foo" => "bar",
"bar" => array(
"st" => "fr",
"gr" => "et",
),
);
What is the fastest method to achieve this?
what i have though about.
foreach (array as key => value) {
recursiveLoop($multidemsnionalArray, $key, $value)
}
function recursiveLoop($multidemsnionalArray, $key, $value){
foreach ($multidemsnionalArrayas as $key2 => $value2) {
if (is_array($value2) or ($value2 instanceof Traversable)){
recursiveLoop($value2, $key, $value);
}
else{
//do the Foo
}
}
}
This is very ineffcient, the time will be according to the length of the first array times the length of the second.
would i best do this or try something else?

How to check if a specific value exists at a specific key in any subarray of a multidimensional array?

I need to search a multidimensional array for a specific value in any of the indexed subarrays.
In other words, I need to check a single column of the multidimensional array for a value. If the value exists anywhere in the multidimensional array, I would like to return true otherwise false
$my_array = array(
0 => array(
"name" => "john",
"id" => 4
),
1 => array(
"name" => "mark",
"id" => 152
),
2 => array(
"name" => "Eduard",
"id" => 152
)
);
I would like to know the fastest and most efficient way to check if the array $my_array contains a value with the key "id". For example, if id => 152 anywhere in the multidimensional array, I would like true.
Nothing will be faster than a simple loop. You can mix-and-match some array functions to do it, but they'll just be implemented as a loop too.
function whatever($array, $key, $val) {
foreach ($array as $item)
if (isset($item[$key]) && $item[$key] == $val)
return true;
return false;
}
The simplest way is this:
$my_array = array(
0 => array(
"name" => "john",
"id" => 4
),
1 => array(
"name" => "mark",
"id" => 152
),
2 => array(
"name" => "Eduard",
"id" => 152
)
);
if (array_search(152, array_column($my_array, 'id')) !== FALSE) {
echo 'FOUND!';
} else {
echo 'NOT FOUND!';
}
** PHP >= 5.5
simply u can use this
$key = array_search(40489, array_column($userdb, 'uid'));
Let's suppose this multi dimensional array:
$userdb=Array
(
(0) => Array
(
(uid) => '100',
(name) => 'Sandra Shush',
(url) => 'urlof100'
),
(1) => Array
(
(uid) => '5465',
(name) => 'Stefanie Mcmohn',
(pic_square) => 'urlof100'
),
(2) => Array
(
(uid) => '40489',
(name) => 'Michael',
(pic_square) => 'urlof40489'
)
);
$key = array_search(40489, array_column($userdb, 'uid'));
Here is an updated version of Dan Grossman's answer which will cater for multidimensional arrays (what I was after):
function find_key_value($array, $key, $val)
{
foreach ($array as $item)
{
if (is_array($item) && find_key_value($item, $key, $val)) return true;
if (isset($item[$key]) && $item[$key] == $val) return true;
}
return false;
}
If you have to make a lot of "id" lookups and it should be really fast you should use a second array containing all the "ids" as keys:
$lookup_array=array();
foreach($my_array as $arr){
$lookup_array[$arr['id']]=1;
}
Now you can check for an existing id very fast, for example:
echo (isset($lookup_array[152]))?'yes':'no';
A good solution can be one provided by #Elias Van Ootegan in a comment that is:
$ids = array_column($array, 'id', 'id');
echo isset($ids[40489])?"Exist":"Not Exist";
I tried it and worked for me, thanks buddy.
Edited
Note: It will work in PHP 5.5+
TMTOWTDI. Here are several solutions in order of complexity.
(Short primer on complexity follows):O(n) or "big o" means worst case scenario where n means the number of elements in the array, and o(n) or "little o" means best case scenario. Long discrete math story short, you only really have to worry about the worst case scenario, and make sure it's not n ^ 2 or n!. It's more a measure of change in computing time as n increases than it is overall computing time. Wikipedia has a good article about computational aka time complexity.
If experience has taught me anything, it's that spending too much time optimizing your programs' little-o is a distinct waste of time better spent doing something - anything - better.
Solution 0: O(n) / o(1) complexity:
This solution has a best case scenario of 1 comparison - 1 iteration thru the loop, but only provided the matching value is in position 0 of the array. The worst case scenario is it's not in the array, and thus has to iterate over every element of the array.
foreach ($my_array as $sub_array) {
if (#$sub_array['id'] === 152) {
return true;
}
}
return false;
Solution 1: O(n) / o(n) complexity:
This solution must loop thru the entire array no matter where the matching value is, so it's always going to be n iterations thru the array.
return 0 < count(
array_filter(
$my_array,
function ($a) {
return array_key_exists('id', $a) && $a['id'] == 152;
}
)
);
Solution 2: O(n log n) / o(n log n) complexity:
A hash insertion is where the log n comes from; n hash insertions = n * log n. There's a hash lookup at the end which is another log n but it's not included because that's just how discrete math works.
$existence_hash = [];
foreach ($my_array as $sub_array) {
$existence_hash[$sub_array['id']] = true;
}
return #$existence_hash['152'];
I came upon this post looking to do the same and came up with my own solution I wanted to offer for future visitors of this page (and to see if doing this way presents any problems I had not forseen).
If you want to get a simple true or false output and want to do this with one line of code without a function or a loop you could serialize the array and then use stripos to search for the value:
stripos(serialize($my_array),$needle)
It seems to work for me.
As in your question, which is actually a simple 2-D array wouldn't it be better? Have a look-
Let say your 2-D array name $my_array and value to find is $id
function idExists($needle='', $haystack=array()){
//now go through each internal array
foreach ($haystack as $item) {
if ($item['id']===$needle) {
return true;
}
}
return false;
}
and to call it:
idExists($id, $my_array);
As you can see, it actually only check if any internal index with key_name 'id' only, have your $value. Some other answers here might also result true if key_name 'name' also has $value
I don't know if this is better or worse for performance, but here is an alternative:
$keys = array_map(function($element){return $element['id'];}, $my_array);
$flipped_keys = array_flip($keys);
if(isset($flipped_keys[40489]))
{
// true
}
You can create a queue of sub-arrays and loop each:
function existsKeyValue($myArray, $key, $value) {
$queue = [$myArray]; //creating a queue of a single element, which is our outermost array
//when we reach the count of the queue we looped all inner loops as well and failed to find the item
for ($index = 0; $index < count($queue); $index++) {
//Looping the current array, finding the key and the value
foreach ($queue[$index] as $k => &$v) {
//If they match the search, then we can return true
if (($key === $k) && ($value === $v)) {
return true;
}
//We need to make sure we did not already loop our current array to avoid infinite cycles
if (is_array($v)) $queue[]=$v;
}
}
return false;
}
$my_array = array(
0 => array(
"name" => "john",
"id" => 4
),
1 => array(
"name" => "mark",
"id" => 152
),
2 => array(
"name" => "Eduard",
"id" => 152
)
);
echo var_dump(existsKeyValue($my_array, 'id', 152));
array_column returns the values of a single column of an array and we can search for a specific value for those via in_array
if (in_array(152, array_column($my_array, 'id'))) {
echo 'FOUND!';
} else {
echo 'NOT FOUND!';
}
Try with this below code. It should be working fine for any kind of multidimensional array search.
Here you can see LIVE DEMO EXAMPLE
function multi_array_search($search_for, $search_in) {
foreach ($search_in as $element) {
if ( ($element === $search_for) ){
return true;
}elseif(is_array($element)){
$result = multi_array_search($search_for, $element);
if($result == true)
return true;
}
}
return false;
}
You can use this with only two parameter
function whatever($array, $val) {
foreach ($array as $item)
if (isset($item) && in_array($val,$item))
return 1;
return 0;
}
different between isset vs array_key_exits
What's the difference between isset() and array_key_exists()?
different between == vs === How do the PHP equality (== double equals) and identity (=== triple equals) comparison operators differ?
function specificValue(array $array,$key,$val) {
foreach ($array as $item)
if (array_key_exits($item[$key]) && $item[$key] === $val)
return true;
return false;
}
function checkMultiArrayValue($array) {
global $test;
foreach ($array as $key => $item) {
if(!empty($item) && is_array($item)) {
checkMultiArrayValue($item);
}else {
if($item)
$test[$key] = $item;
}
}
return $test;
}
$multiArray = array(
0 => array(
"country" => "",
"price" => 4,
"discount-price" => 0,
),);
$test = checkMultiArrayValue($multiArray);
echo "<pre>"
print_r($test);
Will return array who have index and value
I wrote the following function in order to determine if an multidimensional array partially contains a certain value.
function findKeyValue ($array, $needle, $value, $found = false){
foreach ($array as $key => $item){
// Navigate through the array completely.
if (is_array($item)){
$found = $this->findKeyValue($item, $needle, $value, $found);
}
// If the item is a node, verify if the value of the node contains
// the given search parameter. E.G.: 'value' <=> 'This contains the value'
if ( ! empty($key) && $key == $needle && strpos($item, $value) !== false){
return true;
}
}
return $found;
}
Call the function like this:
$this->findKeyValue($array, $key, $value);

Categories