Access Multidimensional Array element with out knowing parent elements - php

I have function that returns the following multidimensional array. I don't have control of how the array is formed. Im trying to access the 'Result' elements. This issue is, the name of the parent elements constantly changing. The location of the 'Result' element is always the same (as the is the name "Result"). Is it possible to access that element without know the name of the parent elements?
Array
(
[sHeader] => Array
(
[aAction] => ActionHere
)
[sBody] => Array
(
[CreatePropertyResponse] => Array
(
[CreatePropertyResult] => Array
(
[Message] => Successfully completed the operation
[Result] => 0
[TransactionDate] => 2013-05-19T21:54:35.765625Z
[bPropertyId] => 103
)
)
)
)

An easy option to search the array keys/values recursively is to use a recursive iterator; these are built-in classes, part of the Standard PHP Library.
$result = false;
$iterator = new RecursiveIteratorIterator(new RecursiveArrayIterator($array));
foreach ($iterator as $key => $value) {
if ($key === 'Result') {
$result = $value;
break;
}
}
var_dump($result);
The bonus here is that you could, if you wanted to, check the depth of the Result item ($iterator->getDepth()) in the array structure and/or check one or more ancestor keys ($iterator->getSubIterator(…)->key()).

If the parent elements have only one child, you can solve it by getting the only element given back by array_keys(), and going two levels deep.
Anyway, if your array changes that much, and you systematically have to access a nested property, you have a design issue for sure.

Edit: array_column won't actually work in this case. You could search through each level, recursively, until you find the given key. Something like:
function find_key_value($array, $search_key) {
if (isset($array[$search_key])) return $array[$search_key];
$found = false;
foreach ($array as $key=>$value) {
if (is_array($value)) $found = find_key_value($value, $search_key);
if ($found) return $found;
}
return false;
}

function findkeyval($arr,$key) {
if(isset($arr[$key])) {
return $arr[$key];
}else {
foreach($arr as $a) {
if(is_array($a)) {
$val=findkeyval($a,$key);
if($val) {
return $val;
}
}
}
}
}

Related

In PHP how do I remove duplicates in an array of objects where a duplicate is defined as a subset of key-value pairs having the same value

I have an array of the form:
class anim {
public $qs;
public $dp;
public $cg;
public $timestamp;
}
$animArray = array();
$myAnim = new anim();
$myAnim->qs = "fred";
$myAnim->dp = "shorts";
$myAnim->cg = "dino";
$myAnim->timestamp = 1590157029399;
$animArray[] = $myAnim;
$myAnim = new anim();
$myAnim->qs = "barney";
$myAnim->dp = "tshirt";
$myAnim->cg = "bird";
$myAnim->timestamp = 1590133656330;
$animArray[] = $myAnim;
$myAnim = new anim();
$myAnim->qs = "fred";
$myAnim->dp = "tshirt";
$myAnim->cg = "bird";
$myAnim->timestamp = 1590117032286;
$animArray[] = $myAnim;
How do I create a new array containing only the non-duplicates (and the latest entry where duplicates are found) of $animArray, where a duplicate is defined as:
one where $myAnim->dp has the same value as that of another array element's $myAnim->dp AND the $myAnim->cg from the first and the $myAnim->cg from the second have the same value as each other.
In the example above, only the first element is unique by that definition.
I'm hoping there's an elegant solution. I've been through all the array functions in the PHP manual but can't see how it could be achieved.
I could loop through each array element checking if $myAnim->dp has the same value as that of another array element's $myAnim->dp, saving the matches into a new array and then looping through that new array, checking for its $myAnim->cg matching the $myAnim->cg of any other element in that new array.
A more elegant solution would allow me to to change which combination of key-value pairs determine whether there's a duplicate, without having to recast much code.
Does such a solution exist?
Thanks for helping this novice :)
While there is nothing built-in that can be used directly out of the box, there isn't a lot of code necessary to handle an arbitrary number of properties to consider for uniqueness. By keeping track of each unique property in a lookup array, we can build an array where the leaf nodes (i.e. the ones that isn't arrays themselves) are the objects.
We do this by keeping a reference (&) to the current level in the array, then continue building our lookup array for each property.
function find_uniques($list, $properties) {
$lookup = [];
$unique = [];
$last_idx = count($properties) - 1;
// Build our lookup array - the leaf nodes will be the items themselves,
// located on a level that matches the number of properties to look at
// to consider a duplicate
foreach ($list as $item) {
$current = &$lookup;
foreach ($properties as $idx => $property) {
// last level, keep object for future reference
if ($idx == $last_idx) {
$current[$item->$property] = $item;
break;
} else if (!isset($current[$item->$property])) {
// otherwise, if not already set, create empty array
$current[$item->$property] = [];
}
// next iteration starts on this level as its current level
$current = &$current[$item->$property];
}
}
// awr only calls the callback for leaf nodes - i.e. our items.
array_walk_recursive($lookup, function ($item) use (&$unique) {
$unique[] = $item;
});
return $unique;
}
Called with your data above, and the requirement being that uniques and the last element of duplicates being returned, we get the following result:
var_dump(find_uniques($animArray, ['dp', 'cg']));
array(2) {
[0] =>
class anim#1 (4) {
public $qs =>
string(4) "fred"
public $dp =>
string(6) "shorts"
public $cg =>
string(4) "dino"
public $timestamp =>
int(1590157029399)
}
[1] =>
class anim#3 (4) {
public $qs =>
string(4) "fred"
public $dp =>
string(6) "tshirt"
public $cg =>
string(4) "bird"
public $timestamp =>
int(1590117032286)
}
}
Which maps to element [0] and element [2] in your example. If you instead want to keep the first object for duplicates, add an isset that terminates the inner loop if property value has been seen already:
foreach ($properties as $idx => $property) {
if ($idx == $last_idx) {
if (isset($current[$item->$property])) {
break;
}
$current[$item->$property] = $item;
} else {
$current[$item->$property] = [];
}
// next iteration starts on this level as its current level
$current = &$current[$item->$property];
}
It's important to note that this has been written with the assumption that the array you want to check for uniqueness doesn't contain arrays themselves (since we're looking up properties with -> and since we're using array_walk_recursive to find anything that isn't an array).
This was fun:
array_multisort(array_column($animArray, 'timestamp'), SORT_DESC, $animArray);
$result = array_intersect_key($animArray,
array_unique(array_map(function($v) { return $v->dp.'-'.$v->cg; }, $animArray)));
First, extract the timestamp and sort that array descending, thereby sorting the original array.
Then, map to create a new array using the dp and cg combinations.
Next, make the combination array unique which will keep the first duplicate encountered (that's why we sorted descending).
Finally, get the intersection of keys of the original array and the unique one.
In a function with dynamic properties:
function array_unique_custom($array, $props) {
array_multisort(array_column($array, 'timestamp'), SORT_DESC, $array);
$result = array_intersect_key($array,
array_unique(array_map(function($v) use ($props) {
return implode('-', array_map(function($p) use($v) { return $v->$p; }, $props));;
},
$array)));
return $result;
}
$result = array_unique_custom($animArray, ['dp', 'cg']);
Another option would be to sort it ascending and then build an array with a dp and cg combination as the key, which will keep the last duplicate:
array_multisort(array_column($animArray, 'timestamp'), SORT_ASC, $animArray);
foreach($animArray as $v) {
$result[$v->dp.'-'.$v->cg] = $v;
}
In a function with dynamic properties:
function array_unique_custom($array, $props) {
array_multisort(array_column($array, 'timestamp'), SORT_ASC, $array);
foreach($array as $v) {
$key = implode(array_map(function($p) use($v) { return $v->$p; }, $props));
$result[$key] = $v;
}
return $result;
}
$result = array_unique_custom($animArray, ['dp', 'cg']);
//Create an array with dp and cg values only
$new_arr = [];
foreach($animArray as $key=>$item) {
$new_arr[] = $item->dp.','.$item->cg;
}
$cvs = array_count_values($new_arr);
$final_array = [];
foreach($cvs as $cvs_key=>$occurences) {
if ($occurences == 1) {
$filter_key = array_keys($new_arr, $cvs_key)[0];
$final_array[$filter_key] = $animArray[$filter_key];
}
}
The final result would be (from your example) in $final_array:
[0] => anim Object
(
[qs] => fred
[dp] => shorts
[cg] => dino
[timestamp] => 1590157029399
)
Some explanation:
//Create a new array based on your array of objects with the attributes dp and cg
//with a comma between them
$new_arr = [];
foreach($animArray as $key=>$item) {
$new_arr[] = $item->dp.','.$item->cg;
}
/*
$new_arr now contains:
[0] => shorts,dino
[1] => tshirt,bird
[2] => tshirt,bird
*/
//Use builtin-function array_count_values to get the nr of occurences for
//each item in an array
$cvs = array_count_values($new_arr);
/*
$cvs would contain:
(
[shorts,dino] => 1
[tshirt,bird] => 2
)
*/
//Iterate through the $cvs array.
//Where there are only one occurence (no duplicates)
//create a final array $final_array
$final_array = [];
foreach($cvs as $cvs_key=>$occurences) {
if ($occurences == 1) {
/*
array_keys with second argument $csv_key searches for key with
with the key from $cvs-key
so basically search for:
shorts,dino and retrieve the key 0 (first element)
*/
$filter_key = array_keys($new_arr, $cvs_key)[0];
/*
Add a new item to the $final_array based on the key in
the original array $animArray
if you don't want the original key in the new array
you could just do $final_array[] instead of
$final_array[$filter_key]
*/
$final_array[$filter_key] = $animArray[$filter_key];
}
}
You said you would like to have some kind of functionality test different attributes. I believe it would just be making a function/method where you pass in two values to the arguments $attr1 ('dp'?), $attr2('cg'?) or similar.
UPDATE
I had not grasped that you wanted the last value as well. This actually seemed as an easier task. Maybe I am missing something but it was fun to come up with a different approach than other answers :-)
//Create an array with dp and cg values only
$new_arr = [];
foreach($animArray as $key=>$item) {
$new_arr[] = $item->dp.','.$item->cg;
}
//Sort keys descending order
krsort($new_arr);
//Because of sending order of keys above, the unique values would return the
//last item of the duplicates
$new_arr2 = array_unique($new_arr);
//Switch order of keys back to normal (ascending)
ksort($new_arr2);
//Create a new array based on the keys set in $new_arr2
//
$final_arr = [];
foreach($new_arr2 as $key=>$item) {
$final_arr[] = $animArray[$key];
}
The output of $final_arr[] would be (in your example)
Array
(
[0] => anim Object
(
[qs] => fred
[dp] => shorts
[cg] => dino
[timestamp] => 1590157029399
)
[1] => anim Object
(
[qs] => fred
[dp] => tshirt
[cg] => bird
[timestamp] => 1590117032286
)
)

How to validate if two dates in array are not same with Carbon

I am using the Laravel framework, I have an array of dates where I want to check if there are two same dates exist. The array is this
array:5 [▼
0 => "2020-04-11"
1 => "2020-04-11"
2 => "2020-04-12"
3 => "2020-04-13"
4 => "2020-04-21"
]
I have written the following function to check, it works but I curious if there is any better way to achieve this because I have to extend it soon so there will be more nested loops.
private function validateFlyingDatesAreOverlapping($flyingDates)
{
foreach ($flyingDates as $key => $datePick) {
$datePickInstance = Carbon::parse($datePick)->startOfDay();
foreach ($flyingDates as $index => $dateCompare) {
$dateCompare = Carbon::parse($dateCompare)->startOfDay();
if ($key != $index) {
$result = $datePickInstance->eq($dateCompare);
if ($result) {
return true;
}
}
}
}
return false;
}
You can use collection unique and count method:
$is_same_exists = !(collect($data)->count() == collect($data)->unique()->count());
If you just need to know if duplicates is exists without getting the values (your function returns boolean, so looks like this is what you need), then you can just compare the count of items in the initial array with count of unique items in array, like this:
if (count(array_unique($array)) != count($array)) {
//duplicates is exists
}

foreach loop multidimensional array but only loop through array elements with specific key

I am creating following array that contains all products and all their categories:
$result = $wpdb->get_results("SELECT product_nr, category FROM erp_product_categories",ARRAY_A);
$product_categories = array();
foreach($result as $row){
$product_categories[$row["product_nr"]][] = $row["category"];
}
(product_nr is an integer and category is a string)
Then i want to check if one of the categories of a product matches with an other variable and return true if thats the case:
foreach($product_categories[$ean] as $product_categorie) {
$manages_post = in_array( $product_categorie, $this->term_link_conditions );
if($manages_post == true){
break;
}
}
return $manages_post;
But I am getting the error
Invalid argument supplied for foreach()
is it not possible to loop only through elements of an array with a specific key?
Edit:
The array looks like this
Array
(
[10001] => Array //product_nr
(
[0] => 1 //category
[1] => 4 //category
)
[10002] => Array
(
[0] => 1
[1] => 20
)
//...
)
You should check that what you are passing to foreach is an array by using the is_array function
If you are not sure it's going to be an array you can always check using the following PHP example code:
if (is_array($product_categories[$ean])) {
foreach ($product_categories[$ean] as $product_categorie) {
//do something
}
}
Check out all your foreach statements, and look if the thing before the as, to make sure it is actually an array. Usevar_dump to dump it.
Try this :
if(is_array($product_categories) && sizeof($product_categories) > 0) {
foreach($product_categories as $key => $product_categorie) {
if($manages_post = in_array($key, $this->term_link_conditions)){
return $manages_post;
}
}
}
I figured out a way to do this
$product_category = $product_categories[$ean];
if (is_array($product_category)) {
$matches = array_intersect($product_category, $this->term_link_conditions);
if(sizeof($matches) > 0){
$manages_post = true;
}
}

recursive array search for key in PHP

I am trying to do an array_search to find the associated value pair
I have an array called $saved_data it contains
Array () {
Client_Information_1 => James
Client_Information_2 => Doe
....
}
I need to return the value (1st call -> James .. 2nd call -> Doe .. etc) each time I call it. the problem is it's not returning the value pair back to me. The needle contains the index "Client_Information_1" .
my solution :
function recursive_array_search($saved_forms, $needle)
{
foreach($saved_forms as $key => $value)
{
if ( $saved_forms[$key] === $needle )
return $key;
}
return false;
}
function call in my loop :
$return_field = recursive_array_search($saved_data,$needle);
The $key is what you're searching for and $value is what you want to return (they value at that index)
So the if statement should look like this:
if ( $key === $needle ) {
return $value;
}
Since your function is not recursive at all or does anything else special, this'll do the same thing just fine:
$return_field = isset($saved_data[$needle]) ? $saved_data[$needle] : false;

Searching for parts of string in multi level array

Is there a function that allows me to search inside a multidimensional array that goes multiple levels deep? An example of an array can be found below.
What I want is to be able to search in the entire array, regardless of how deep it goes (3 levels deep would be the practical limit though). The search must be done in all of the strings inside the array elements, and to make it even more complex, it needs to be able to find parts of a string in the array (preferably case insensitive).
I've searched for a good class or function that can handle this in a fast and efficient way, but haven't found one so far.
Array
(
[0] => Array
(
[OrderReferenceNumber] => 201100196
[OrderCustomerID] => 01239123
[OrderCustomerName] => test
[OrderHistoryItems] => Array
(
[0] => Array
(
[OrderItem] => productID
[OrderItemGroup] => productName
)
)
)
Much appreciated!
This will search all elements on all levels of the array, and the search is case sensitive. Just pass the search term and the array to search.
class mySearcher
{
protected $search = '';
public function search($search, $arr)
{
$this->search = $search;
$this->searchArr($arr);
}
protected function searchArr($arr)
{
if(is_array($arr))
{
foreach($arr as $value)
{
if(is_array($value))
{
$this->searchArr($value); // this element is an array, so recursively search it
}
else
{
if(stripos($value, $this->search) !== false)
{
// found the search term, do something
echo 'found in: ' . $value . '<br />';
}
}
}
}
}
}
$obj = new mySearcher();
$obj->search('test', $arr); // search for 'test' in the array called $arr

Categories