Calculating the depth of an element in an associative array with php - php

I've been trying unsuccessfully to write a recursive function to give me the depth of a given element. I just can't seem to get my head around the recursion. This is what I have, but it's not working correctly:
function getDepth($a, $e, $depth = 0) {
foreach ($a as $key => $val) {
if (is_array($val)) {
if ($key == $e) {
return $depth;
}
return getDepth($val, $e, $depth + 1);
}
else {
if ($val == $e) {
return $depth;
}
else {
return 1;
}
}
}
}
Can anyone please help point out what I'm doing wrong here? Thanks in advance for your help.
EDIT:
#Brad #Amber #Viktor Thanks, but this doesn't seem to be working either. Here's what I'm after... I have an array that looks like this:
[antonio] => Array
(
[ian] => Array
(
[molly] => Array
(
[blake] => blake
)
)
[shonda] => Array
(
[dana] => dana
[james] => james
)
[nels] => Array
(
[jason] => jason
[danny] => danny
)
[molly] => Array
(
[blake] => blake
)
[blake] => blake
[travis] => travis
)
It's a tree, and I'm hoping to find the depth level of a given name. So, I'll need to pass in a name, say blake. Then I'll want to traverse the whole tree keeping track of blake's depth whenever I find it as he could be (and indeed is in this example) in the tree at different levels. Assuming that the top-most depth level is 0, blake's level under antonio => ian => molly => blake is 3, but his level under antonio => blake is 1, so I would want to return 1. I will have to traverse the entire tree (luckily this function will not be called very often) to make sure that I've found the most shallow depth in the tree for the given user. Thanks again for your help.

Basically, to get the recursion right, if you know you have an array within your function, run your same function on it. We add the deepest path so far +1. In the end, you get what you are looking for.
function getDepth($a) {
$max=0;
foreach ($a as $val) {
if (is_array($val)) {
$tmp_depth=getDepth($val);
if ($max<($tmp_depth)) {
$max=$tmp_depth;
}
}
}
return $max+1;
}
I haven't benchmarked this or anything. Undoubtedly there could be speed improvements, if it is important.

function getDepth($a, $e, $depth = 0) {
$lower = false; // meaning 'not found'
// as you are looking for the less deep value, first check for values and then for arrays
// so there are chances when you do not traverse all the tree for a match
foreach ($a as $key => $val) if ($val == $e) return $depth;
foreach ($a as $key => $val) {
if (is_array($val)) {
$tmp = getDepth($val, $e, $depth + 1);
if($tmp) if ($lower === false || $tmp < $lower) $lower = $tmp;
}
}
return $lower;
}
$x=array(
antonio => Array(
ian => Array(
molly => Array(
blake => blake,
jhonyDeep => jhonyDeep
)
),
shonda => Array(
dana => dana,
james => james
),
nels => Array (
jason => jason,
danny => danny
),
molly => Array(
blake => blake
),
blake => blake,
travis => travis
)
);
echo getDepth($x, blake); // gives 1
echo getDepth($x, danny); // gives 2
echo getDepth($x, jhonyDeep); // gives 3

similar to my previous anwser but optimized for performance (and be careful, not tested)
function getDepth($a, $e, $depth = 0, $lower = false) {
// $lower = false means 'not found'
// as you are looking for the less deep value, first check for values and then for arrays
// so there are chances when you do not traverse all the tree for a match
foreach ($a as $key => $val) if ($val == $e) return $depth;
foreach ($a as $key => $val) {
if (is_array($val)) {
$tmp = false;
if($lower===false || $lower > $depth) $tmp = getDepth($val, $e, $depth + 1, $lower); // to do not recurse innecesary
if($tmp) {
if($tmp==$depth+1) return $tmp; //optimization: $depth+1 can't be beat by other values
if ($lower === false || $tmp < $lower) $lower = $tmp;
}
}
}
return $lower;
}
$x=array(
antonio => Array(
ian => Array(
molly => Array(
blake => blake,
jhonyDeep => jhonyDeep
)
),
shonda => Array(
dana => dana,
james => james
),
nels => Array (
jason => jason,
danny => danny
),
molly => Array(
blake => blake
),
blake => blake,
travis => travis
)
);
echo getDepth($x, blake); // gives 1
echo getDepth($x, danny); // gives 2
echo getDepth($x, jhonyDeep); // gives 3

Related

Fastest return TRUE if all values of a column of a multidimensional array is_numeric

Are there faster methods to check the existence of a number (not null) in one
column of a multidimensional array in php (for instance, number9)?
Attempt:
The if statement below seems to be working okay.
$arr=Array(
[0] => Array
(
[date] => 2019-01-16
[number1] => 20.4
[number2] => 20.54
[number3] => 19.71
[number4] => 19.73
[number5] => 70849266
[number6] => 70849266
[number7] => -0.65
[number8] => -3.189
[number9] => 20.0902
[string1] => Jan 16
[number10] => 0.047796070100903
)
.
.
.
[21] => Array
(
[date] => 2019-01-17
[number1 => 19.49
[number2] => 20.51
[number3] => 19.02
[number4] => 20.25
[number5] => 85018421
[number6] => 85018421
[number7] => 0.52
[number8] => 2.636
[number9] => 19.7988
[string1] => Jan 17
[number10] => 0.075411577270313
)
);
function isNumeric($var){
if (is_numeric($var)){
return true;
} else {
return false;
}
}
if((array_walk(array_column($arr, "number8"), 'isNumeric'))!=1)
Here are my ideas.
First is to just filter the array for numeric only values and compare to the original:
function with_array_filter($arr) {
return $arr == array_filter($arr, 'is_numeric');
}
The second example uses casting to a float and then back to string, keep in mind that this is not going to be accurate for very big numbers, however seems to be the fastest (if you care about that at all):
function is_numeric_array_with_cast($arr) {
foreach ($arr as $b) {
if ($b != (string)(float)$b) {
return false;
}
}
return true;
}
However probably the simplest solution is to just foreach the array inside a function and return early:
function is_numeric_array_with_func($arr) {
foreach ($arr as $b) {
if (!is_numeric($b)) {
return false;
}
}
return true;
}
Benchmarked with an array of 20 elements over 100000 iterations on PHP 7.2:
$falseArray = [
'1',
2.5,
-10,
32,
11.0,
2.5,
100101221,
345,
-10,
'-10',
'+10',
'10',
12,
PHP_INT_MAX,
PHP_INT_MAX + 1.4e5,
'-10',
null,
'a',
'5',
5
];
Matt Fryer
Time: 4.8473789691925
is_numeric_array_with_func
Time: 4.0416791439056
is_numeric_array_with_cast
Time: 3.2953300476074
with_array_filter
Time: 3.99729180336
AS I said in the comments:
The if statement below seems to be working okay
However, given the code you posed I doubt that: lets look at it.
function isNumeric($var){ ... }
if(array_walk(array_column($arr, "number8"), 'isNumberic'))!=1
The first and most obvious things are these 2
isNumberic vs isNumeric, which is a fatal undefined function error (spelling/typo).
)!=1 then this is outside of the actual condition, or put another way if(...){ !=1 }
Let's assume those are just typos in the question. Even if your code was free of the 2 "defects" I mentioned above you would still have this problem, array_walk works by reference and simply returns true (always). Pass by reference updates the "Original" variable without returning a copy of it (in the case of array_walk)
http://php.net/manual/en/function.array-walk.php
Return Values
Returns TRUE.
Which of course just makes your condition pass no matter what. So you should always test both the passing and the failing of the condition (As I did by placing some "canned" bad data in there). This way I know for 100% sure exactly how my code behaves.
Others have posted how to correct this, but not what you did wrong. But just for the sake of completeness I will post an answer anyway.
$arr = array (
0 =>
array (
'date' => '2019-01-16',
'number1' => 20.4,
'number2' => 20.54,
'number3' => 19.71,
'number4' => 19.73,
'number5' => 70849266,
'number6' => 70849266,
'number7' => -0.65,
'number8' => -3.189,
'number9' => 20.0902,
'string1' => 'Jan16',
'number10' => 0.047796070100903
),
array (
'date' => '2019-01-16',
'number1' => 20.4,
'number2' => 20.54,
'number3' => 19.71,
'number4' => 19.73,
'number5' => 70849266,
'number6' => 70849266,
'number7' => -0.65,
'number8' => 'foo',#intentially not number
'number9' => 20.0902,
'string1' => 'Jan16',
'number10' => 0.047796070100903
),
);
$col = array_column($arr, "number8");
$col1 = array_filter($col, 'is_numeric');
if($col != $col1){
echo "not the same\n";
}
Output:
not the same
Sandbox
I should mention, there is no "need" to count these, as PHP can compare complex objects for equality. As we are comparing the same "root" array ($col in this example) with itself after (possibly) removing some elements, if no elements were removed then both arrays should be not only the same length but also "Identical".
Also if you want to do it in one line (inside the condition) you can do this:
if( ($col = array_column($arr, "number8")) && $col != array_filter($col, 'is_numeric')){
echo "not the same\n";
}
It's a bit harder to read, and pay attention to $col = array_column assignment.
Use a foreach loop:
$bool = true;
foreach ($arr as $row)
{
if (!is_numeric($row['number8']))
{
$bool = false;
break;
}
}
Thanks a million everyone, for your great answers!
On my PC, I tried your four functions in PHP 5.5.38 with ~5000 iterations and total times are:
"is_numeric_array_with_cast total time is 0.44153618812561"
"with_array_filter total time is 0.21628260612488"
"is_numeric_array_with_func total time is 0.14269280433655"
"is_numeric_matt_fryer total time is 0.155033826828"
$t1=$t2=$t3=$t4=0;
foreach($arrs as $k=>$arr){
$s1=microtime(true);
is_numeric_array_with_cast(array_column($arr, "number8"));
$e1=microtime(true)-$s1;
$t1=$t1+$e1;
$s2=microtime(true);
with_array_filter(array_column($arr, "number8"));
$e2=microtime(true)-$s2;
$t2=$t2+$e2;
$s3=microtime(true);
is_numeric_array_with_func(array_column($arr, "number8"));
$e3=microtime(true)-$s3;
$t3=$t3+$e3;
$s4=microtime(true);
is_numeric_matt_fryer(array_column($arr, "number8"),"number8");
$e4=microtime(true)-$s4;
$t4=$t4+$e4;
}
function is_numeric_array_with_cast($arr) {
foreach ($arr as $b) {
if ($b != (string)(float)$b) {
return false;
}
}
return true;
}
function with_array_filter($arr) {
return $arr == array_filter($arr, 'is_numeric');
}
function is_numeric_array_with_func($arr) {
foreach ($arr as $b) {
if (!is_numeric($b)) {
return false;
}
}
return true;
}
function is_numeric_matt_fryer($arr,$str){
$bool = true;
foreach ($arr as $row)
{
if (!is_numeric($row[$str]))
{
$bool = false;
}
}
return $bool;
}

Taking a string of period separated properties and converting it to a json object in php

I'm fairly sure I'm missing something blindingly obvious here but here it goes.
I am working on updating a search function in an application which was running a loop and doing a very large number of sql queries to get object / table relations to one large query that returns everything. However the only way I could think to return relations was period separated, what I am now wanting to do is take the flat array of keys and values and convert it into an associative array to then be jsonified with json_encode.
For example what I have is this...
array(
"ID"=>10,
"CompanyName"=>"Some Company",
"CompanyStatusID"=>2,
"CompanyStatus.Status"=>"Active",
"addressID"=>134,
"address.postcode"=>"XXX XXXX",
"address.street"=>"Some Street"
);
And what I want to turn it into is this...
array(
"ID"=>10,
"CompanyName"=>"Some Company",
"CompanyStatusID"=>2,
"CompanyStatus"=>array(
"Status"=>"Active"
),
"addressID"=>134,
"address"=>array(
"postcode"=>"XXX XXXX",
"street"=>"Some Street"
)
);
Now I'm sure this should be a fairly simple recursive loop but for the life of me this morning I can't figure it out.
Any help is greatly appreciated.
Regards
Graham.
Your function was part way there mike, though it had the problem that the top level value kept getting reset on each pass of the array so only the last period separated property made it in.
Please see updated version.
function parse_array($src) {
$dst = array();
foreach($src as $key => $val) {
$parts = explode(".", $key);
if(count($parts) > 1) {
$index = &$dst;
$i = 0;
$count = count($parts)-1;
foreach(array_slice($parts,0) as $part) {
if($i == $count) {
$index[$part] = $val;
} else {
if(!isset($index[$part])){
$index[$part] = array();
}
}
$index = &$index[$part];
$i++;
}
} else {
$dst[$parts[0]] = $val;
}
}
return $dst;
}
I am sure there is something more elegant, but quick and dirty:
$arr = array(
"ID"=>10,
"CompanyName"=>"Some Company",
"CompanyStatusID"=>2,
"CompanyStatus.Status"=>"Active",
"addressID"=>134,
"address.postcode"=>"XXX XXXX",
"address.street"=>"Some Street"
);
$narr = array();
foreach($arr as $key=>$val)
{
if (preg_match("~\.~", $key))
{
$parts = split("\.", $key);
$narr [$parts[0]][$parts[1]] = $val;
}
else $narr [$key] = $val;
}
$arr = array(
"ID" => 10,
"CompanyName" => "Some Company",
"CompanyStatusID" => 2,
"CompanyStatus.Status" => "Active",
"addressID" => 134,
"address.postcode" => "XXX XXXX",
"address.street" => "Some Street",
"1.2.3.4.5" => "Some nested value"
);
function parse_array ($src) {
$dst = array();
foreach($src as $key => $val) {
$parts = explode(".", $key);
$dst[$parts[0]] = $val;
if(count($parts) > 1) {
$index = &$dst[$parts[0]];
foreach(array_slice($parts, 1) as $part) {
$index = array($part => $val);
$index = &$index[$part];
}
}
}
return $dst;
}
print_r(parse_array($arr));
Outputs:
Array
(
[ID] => 10
[CompanyName] => Some Company
[CompanyStatusID] => 2
[CompanyStatus] => Array
(
[Status] => Active
)
[addressID] => 134
[address] => Array
(
[street] => Some Street
)
[1] => Array
(
[2] => Array
(
[3] => Array
(
[4] => Array
(
[5] => Some nested value
)
)
)
)
)

finding the first element that is needed

I have this array
Array
(
[name] => Step1 is here
[standard] => Array
(
[product_id] => 85,99
[product_name] => Step1 is here
[product_price] => 976.0000
[product_description] => :something
[product_image] => http://someurl.com/shop_pos/image/data/13D.png
)
[professional] => Array
(
[product_id] => 61
[product_name] => Step1 is here
[product_price] => 289.0000
[product_description] => somethingothere
[product_image] => http://someurl.com/shop_pos/image/data/13B.png
)
[premium] => Array
(
[product_id] => 677
[product_name] => Step1 is here
[product_price] => 289.0000
[product_description] => somethingothere
[product_image] => http://someurl.com/shop_pos/image/data/13A.png
)
)
Is there an easy of referencing in the proper order that i need. SO the order i need is standard, professional, premium.. so if one is not present can I do to the other like this
if (!isset($my_array['standard'])) {
$use_me = $my_array['standard']
}elseif(!isset($my_array['professional'])) {
$use_me = $my_array['professional']
}elseif(!isset($my_array['professional'])) {
$use_me = $my_array['premium']}
}
i have the above code that i think may work but is there a better way
That should do it:
$keys = array_slice(array_keys($my_array), 1, 1);
$use_me = $my_array[$keys[0]];
In short:
Get the array keys (name, standard, professional, premium). (array_keys)
Get the second key (bypassing name, so it returns standard or whatever the second key is). (array_slice)
Reference $my_array using that key and store it in $use_me.
I would do this:
foreach (array('standard', 'professional', 'premium') as $name) {
if (isset($my_array[$name])) {
$use_me = $my_array[$name];
break;
}
}
or a little more structured:
function selectPlan($array) {
foreach (array('standard', 'professional', 'premium') as $name) {
if (isset($array[$name])) return $array[$name];
}
}
$use_me = selectPlan($my_array);
Yes, you can do it the way you have presented in your code. However, your if statement logic is not correct. Your code should be like this:
if (isset($my_array['standard'])) {
$use_me = $my_array['standard'];
} elseif(isset($my_array['professional'])) {
$use_me = $my_array['professional'];
} elseif(isset($my_array['premium'])) {
$use_me = $my_array['premium'];
}
try with array_key_exists
foreach($input as $k=>$v) {
if (array_key_exists('standard', $k)) {
$output [$k] = $k;
}
if (array_key_exists('professional', $k)) {
$output [$k] = $k;
}
if (array_key_exists('premium', $k)) {
$output [$k] = $k;
}
}
or u can go through array-intersect-key
If I understand correctly what you want to do, and if the order of the keys is not guaranteed to be exactly like the one in the example, you can do something like:
// initialization
$use_me = 'default value';
// go through each package
foreach (array('standard', 'professional', 'premium') as $package) {
// when we find a package that does exist
if (isset($my_array[$package])) {
// mark it as found and exit the loop
$use_me = $package;
break;
}
}
This will go through all the packages and set the $use_me variable to the first found value. If no value is found, it sets it to a default value.

How to use foreach to verify value in a 3-dimensional array without having duplicated output in php?

Hi my question is a little tricky, I got a 3-dimensional array and try to verify the 3rd level value and echo both 1st and 3rd level values.
The following is the code example, and my failed approaches.
$myArray=array(
"mySub0" => arrary(
0 => array("mySubSub0" => "1","mySubSub1" => "a"),
1 => array("mySubSub0" => "2","mySubSub1" => "b"),
2 => array("mySubSub0" => "3","mySubSub1" => "b"),
),
"mySub1" => arrary(
0 => array("mySubSub0" => "4","mySubSub1" => "a"),
1 => array("mySubSub0" => "5","mySubSub1" => "a"),
2 => array("mySubSub0" => "6","mySubSub1" => "a"),
),
"mySub2" => arrary(
0 => array("mySubSub0" => "7","mySubSub1" => "a"),
1 => array("mySubSub0" => "8","mySubSub1" => "b"),
2 => array("mySubSub0" => "9","mySubSub1" => "a"),
),
),
I want to check if the value of "mySubSub1" is b. if yes, echo the value of "mySubSub0" and the related key in first-level of the array. It should be like this:
mySub0
2
3
mySub2
8
My failed approach is
foreach ($myArray as $a => $b)
{
foreach ($b as $c)
if($c[mySubSub1]=="b")
{
echo $a
echo $c[mySubSub0];
}
else {
}
}
The result will have one duplicate mySub0
mySub0
2
mySub0
3
mySub2
8
if I move the "echo $a" out of the "if"
foreach ($myArray as $a => $b)
{
echo $a
foreach ($b as $c)
if($c[mySubSub1]=="b")
{
echo $c[mySubSub0];
}
else {
}
}
the result would be
mySub0
2
3
mySub1
mySub2
8
one unwanted "mySub1" because there is no place to verify if there is a value b.
It has bothered my a lot today. I tried to Google but haven't found the right answer.
Really hope someone can help me. Thank you in advance
Here's something that should work:
function find_value($arr, $key, $value, $sibling)
{
foreach ($arr as $k => $v)
{
$results = _find_value($v, $key, $value, $sibling);
if (count($results) > 0) {
echo $k;
foreach ($results as $result) {
echo $result;
}
}
}
}
function _find_value($arr, $key, $value, $sibling)
{
$out = array();
foreach ($arr as $k => $v)
{
if ($v[$key] == $value)
$out[] = $v[$sibling];
}
return $out;
}
find_value($myArray, "mySubSub1", "b", "mySubSub0");
Basically, the main function loops over items in the outer array, calling the inner function to get a list of the "sibling" keys where the main key matches the value you're looking for. If a non-zero number of results are obtained in the inner function, echo and loop.
Hopefully this does the trick for you.

php - How do I weed out similar items from an associative array?

UPDATE: someone edited my question incorrectly, but it's fixed now
Let's say I have the following associative array:
Array (
[Postponement no issue for Man Utd (BBC) ] => 8
[Postponement no issue for Man Utd ] => 7
[Postponement no issue for Man Utd: Manchester United say they have no issue over Sunday's game at Chelsea being ... ] => 3
)
You'll notice that the term "Postponement no issue for Man Utd" appears in all three keys. I want to turn this array into a new array like this:
Array ( [Postponement no issue for Man Utd] => 18 )
Where 18 is the sum of all values where the key contains "Postponement no issue for Man Utd".
In other words: if one key appears inside another key, the latter is dropped fom the new array and it's value is added on to the former key's value.
Is this possible and how? Thanks in advance.
Depending on the size of your data this may not be feasible. There is almost certainly a more optimal solution, but this works:
<?php
$array = array(
'Postponement no issue for Man Utd (BBC)' => 8,
'Postponement no issue for Man Utd' => 7,
'Postponement no issue for Man Utd: Manchester United say they have no issue over Sunday\'s game at Chelsea being ...' => 3
);
$keys = array_keys($array);
usort($keys, 'lengthCmp');
$flippedKeys = array_flip($keys);
$data = array();
foreach ($keys as $k => $v)
{
$sum = 0;
foreach ($array as $key => $val)
{
if (stripos($key, $v) !== FALSE)
{
$sum += $val;
unset($array[$key]);
unset($keys[$flippedKeys[$v]]);
}
}
$data[$v] = $sum;
}
foreach ($data as $key => $val)
{
if ($val == 0)
unset($data[$key]);
}
var_dump($data);
function lengthCmp($a, $b)
{
return strlen($a) - strlen($b);
}
?>
Output:
array(1) {["Postponement no issue for Man Utd"] => int(18)}
And with this data set:
$array = array(
'test test' => 1,
'Postponement no issue for Man Utd (BBC)' => 8,
'Postponement no issue for Man Utd' => 7,
'Postponement no issue for Man Utd: Manchester United say they have no issue over Sunday\'s game at Chelsea being ...' => 3,
'test' => 1,
'not here' => 1,
'another test' => 1
);
Output:
array(3) {
["test"] => int(3)
["not here"] => int(1)
["Postponement no issue for Man Utd"] => int(18)
}
foreach($array as $k1 => $v1)
foreach($array as $k2 => $v2)
if(strpos($k2, $k1) !== false) // or === 0
$result[$k1] = (isset($result[$k1]) ? $result[$k1] : 0) + $v2;
untested
A simple approach could be:
$original_array = array(...); // The original array with keys and values
asort($original_array); // Sort the array by its keys
$clean_array = array();
foreach ($original_array as $key => $value)
{
foreach (array_keys($clean_array) as $clean_key)
{
$found = false;
if (preg_match('/'.preg_quote($clean_key).'/', $key))
{
$clean_array[$clean_key] += $value;
$found = true;
break;
}
if (!$found) $clean_array[$key] = $value;
}
}
Ok... the only way to do this is to iterate through the array twice, and have a function compare them:
foreach ($my_array as $key1 => $value1) {
foreach ($my_array as $key2 => $value2) {
if ($key1 != $key2) { // don't compare them if they're the same
if (compareKeys($key1,$key2)) {
$my_array[$key1] += $value2;
unset($my_array[$key2]);
}
}
}
}
Then your compareKeys function could look something like this:
function compareKeys($val1,$val2) {
return (strpos($val1,$val2)!==false);
}

Categories