How to properly calculate the amount of levels in an array? - php

I have a function that has to accept an array of points, or an array of array of points (a 2 or 3 dimensional array). I'm looking for an reliable way to detect whether it has 2 or 3 levels. The thing is, I cannot count on the keys of the arrays to do the checking, so this wont work:
$levels = isset($array[0][0]) && is_array($array[0][0]) ? 3 : 2;
..as the first key might not be 0. It usually is, but I don't want to rely on this. And anyways, it's an crappy and a close minded way to do that. Optimally, I would like to check for any number of levels without having to loop through the whole array.
Here's what the arrays might look like:
array(5) {
[2] => array(2) {
[x] => 3
[y] => 6
}
[3] => array(2) {
[x] => 4
[y] => 8
}
...
And a three dimensional array would contain these arrays.
Some notes:
The arrays are large, so completely looping through the arrays is not a very good option
The arrays are numerically and sequentially indexed (with the exception of the last level, which has x and y)
The array keys might or might not start from 0
While writing this, I came up with a solution that might be feasible; a recursive function that checks the first item of an array, if it is, then call itself on the newly found array etc.
Are there any better, cleaner ideas? Bonus points for supporting arrays that might have both scalar values and arrays (eg. the first item of an array might be a string, but the next is an array).

If you expect a complete array or a complete array of arrays then you could try:-
if (isset $points[0][0][0])
If however your array is sparse its more difficult.
The basic problem is that a php "array" is actually a one dimensional hash. The trick being that a value can be another "array". So you need to access to second level to determine whether its a value or an array.
Again if you expect a given array to contain only point values, or only other arrays you only need to check one entry so:
if ( is_array(current(current($points))) )
Should get you what you want: the current() function returns the current array pointer (which defaults to the first - so it will always be set to something), so the inside current($points) would get you $points[0] or the first entry with an actual value, likwise the outside current will get you something like $points[0][0].

I don't see how you could do this without at least iterating through the array. The simple fact is that any one of the elements in your array could have an additional level. As a result, every element needs to be tested.
That being said, you can still use recursion to improve your code a bit:
/**
* Determine the maximum depth of an array.
* #param $input The object to test. Might be an array, or might be an int (or
* any other object).
* #param $startDepth The starting depth. Will be added as an offset to the
* result.
* #return The depth of the array, plus any initial offset specified in
* $startDepth.
*/
function testDepth($input, $startDepth = 0) {
if (is_array($input)) {
$max = $startDepth;
foreach ($input as $i) {
// Check what the depth of the given element is
$result = testDepth($i, $startDepth + 1);
// We only care about the maximum value
if ($result > $max) {
$max = $result;
}
}
return $max;
} else {
// This isn't an array, so it's assumed not to be a container.
// This doesn't add any depth to the parent array, so just return $startDepth
return $startDepth;
}
}
testDepth($array);

$levels = is_array(current(current($array))) ? 3 : 2;

Related

Foreach loop over array of objects Laravel

I'm receiving this array of objects and need to iterate over it, but the problem is that I need to get the value of the next item when its iterating, to compare the value of the current object with the next one, and if it's a different value, split this array in two.
So, I was doing it with next() php function:
//looking for the next register in the array
$next = next($finances);
//first array if exist different values
$aiuaEd = [];
//second array if exist different values
$aiua = [];
foreach ($finances as $finance) {
if ($finance->cnpj <> $next->cnpj) {
$aiua[] = $finance;
} else {
$aiuaEd[] = $finance;
}
}
This code works fine at some point, but then I got this error:
Trying to get property 'cnpj' of non-object in
I don't know why sometimes works well and sometimes don't, debugging this variables, I found that if my array have 2 objects inside only, when I'm looping over it, the $next->cnpj variable came as empty, and sometimes don't.
Can someone help me with this?
I solved it with a different approach, instead of using php next(), I first loop over this array saving the cnpj's into an array.
$cnpjs = [];
foreach($finances as $finance){
$cnpj[] = $finance->cnpj;
}
Then I use array_unique() to group this 2 differents CNPJ's and sort() to get the correct keys order.
//grouping cnpjs as unique, should exist only 2 keys
$cnpj = array_unique($cnpj);
//sort array keys to get in order
sort($cnpj);
Then I iterate over my $finances array again, but now I'm counting if this $cnpj array has more than 2 positions, which means that I have to split this data in two differents arrays.
foreach($finances as $finance){
if(count($cnpj) > 1){
if($finance->cnpj == $cnpj[1]){
$aiua[] = $finance;
}else{
$aiuaEd[] = $finance;
}
}else{
$aiuaEd[] = $finance;
}
}
I'm pretty sure that this is not the best approach for that problem, or at least the most optimized one, so I'm open for new approach's suggestions!
Just posting how I solved my problem in case anyone having the same one.
Notice that this approach is only usable because I know that will not exist more than 2 different's CNPJ's in the array.

Unset Nested Array by Comparison

Thank you for taking your time to look at this question.
I have a database entry that contains a serialized array (of multiple arrays). It might look like this:
Array 1
a:2:{
i:0;a:3:{s:11:"search_type";s:6:"owners";s:11:"search_text";s:4:"test";s:11:"search_name";s:4:"test";}
i:1;a:5:{s:8:"t_rating";s:3:"Yes";s:9:"t_ranking";s:3:"Yes";s:11:"search_type";s:8:"products";s:11:"search_text";s:5:"test2";s:11:"search_name";s:5:"test2";}
}
Then, I have another serialized array being passed that might look like this:
Array 2
a:2:{s:11:"search_type";s:6:"owners";s:11:"search_text";s:4:"test";}
Conditions
Array 1 can have any number of nested arrays (my example shows 2).
Array 2 is passed from a selector; which should loop Array 1 and remove it's associated nested array.
These arrays are being compared using PHP.
The Problem
The issue is that each array in Array 1 needs to first remove the "search_name" array key before making the comparison.
The "search_name" key from Array 1 should never be used for comparison. When the "search_name" key is removed; a valid comparison of the serialized arrays can then be made.
But, when this gets updated to the db; the non-removed arrays should still contain the "search_name" key. It only needs to be removed for comparison; then it's full array should be removed from Array 1.
My (Broken) Code
Here is what I currently have:
$search_array = unserialize($_POST['search_array']); // Serialized Array 2
$bm_adv_search = $query_adv_search[0]->bm_adv_search; // Serialized Array 1
$unser_array = unserialize($bm_adv_search); // Unserialize Array 1
// Unset search_name from each array in Array 1
foreach($unser_array as $key => $value) {
unset($unser_array[$key]['search_name']); // THIS IS THE PROBLEM AREA
}
// Unset Array 2 from Array 1
if(in_array($search_array, $unser_array)) {
if(($key = array_search($search_array, $unser_array)) !== false) {
unset($unser_array[$key]);
}
}
// Re-Serialize Array 1
$reser_array = serialize($unser_array);
// Update db with Array 1
.....
So when I update Array 1 to the db; the name field has been removed from all nested arrays, which when updated, excludes the "search_name".
I need the "search_name" to stay in each array from Array 1 when updated. I just need to remove it for comparison purposes... and then remove the nested array from Array 1.
The Idea
Basically, I am storing user saved bookmarks. When a user bookmarks an item; a serialized array gets added to Array 1. A user is prompted to enter a "Name" for the search bookmark; hence the "search_name" field.
When a user clicks to remove a bookmark; the "search_name" key is not available in the comparison array (Array 2). Array 1 should be looped for Array 2's existence (minus the "search_name" key)... and the entire matched array (including "search_name") should be unset.
Again, thank you for any time on this question. I really appreciate any assistance.
UPDATE
Got it working. Thanks Mikel!!
Here is the updated code:
$search_array = unserialize($_POST['search_array']); // Serialized Array 2
$bm_adv_search = $query_adv_search[0]->bm_adv_search; // Serialized Array 1
$unser_array = unserialize($bm_adv_search); // Unserialize Array 1
// Clone Array 1
$compare_array = $unser_array;
// Unset search_name from each array in cloned Array 1
foreach($compare_array as $key => $value) {
unset($compare_array[$key]['search_name']);
}
// Unset Array 2 from Array 1
if(in_array($search_array, $compare_array)) {
if(($key = array_search($search_array, $compare_array)) !== false) {
unset($unser_array[$key]);
}
}
// Re-Serialize Array 1
$reser_array = serialize($unser_array);
// Update db with Array 1
.....
Copy your "Array 1" to a temporary array for comparison, but do any true modifications to the main $unser_array.
Could be as simple as $compar_array = $unser_array and then modifying a few of the vars throughout the code.
You can then compare the two by array key, as they keys will be identical in the arrays. (you just created this copy)
Lemme know if you need any help!

increment value inside an array of arrays (if key is non-existent, set it to 1)

Question has been updated to clarify
For simple arrays, I find it convenient to use $arr[$key]++ to either populate a new element or increment an existing element. For example, counting the number of fruits, $arr['apple']++ will create the array element $arr('apple'=>1) the first time "apple" is encountered. Subsequent iterations will merely increment the value for "apple". There is no need to add code to check to see if the key "apple" already exists.
I am populating an array of arrays, and want to achieve a similar "one-liner" as in the example above in an element of the nested array.
$stats is the array. Each element in $stats is another array with 2 keys ("name" and "count")
I want to be able to push an array into $stats - if the key already exists, merely increment the "count" value. If it doesn't exist, create a new element array and set the count to 1. And doing this in one line, just like the example above for a simple array.
In code, this would look something like (but does not work):
$stats[$key] = array('name'=>$name,'count'=>++);
or
$stats[$key] = array('name'=>$name,++);
Looking for ideas on how to achieve this without the need to check if the element already exists.
Background:
I am cycling through an array of objects, looking at the "data" element in each one. Here is a snip from the array:
[1] => stdClass Object
(
[to] => stdClass Object
(
[data] => Array
(
[0] => stdClass Object
(
[name] => foobar
[id] => 1234
)
)
)
I would like to count the occurrences of "id" and correlate it to "name". ("id" and "name" are unique combinations - ex. name="foobar" will always have an id=1234)
i.e.
id name count
1234 foobar 55
6789 raboof 99
I'm using an array of arrays at the moment, $stats, to capture the information (I am def. open to other implementations. I looked into array_unique but my original data is deep inside arrays & objects).
The first time I encounter "id" (ex. 1234), I'll create a new array in $stats, and set the count to 1. For subsequent hits (ex: id=1234), I just want to increment count.
For one dimensional arrays, $arr[$obj->id]++ works fine, but I can't figure out how to push/increment for array of arrays. How can I push/increment in one line for multi-dimensional arrays?
Thanks in advance.
$stats = array();
foreach ($dataArray as $element) {
$obj = $element->to->data[0];
// this next line does not meet my needs, it's just to demonstrate the structure of the array
$stats[$obj->id] = array('name'=>$obj->name,'count'=>1);
// this next line obviously does not work, it's what I need to get working
$stats[$obj->id] = array('name'=>$obj->name,'count'=>++);
}
Try checking to see if your array has that value populated, if it's populated then build on that value, otherwise set a default value.
$stats = array();
foreach ($dataArray as $element) {
$obj = $element->to->data[0];
if (!isset($stats[$obj->id])) { // conditionally create array
$stats[$obj->id] = array('name'=>$obj->name,'count'=> 0);
}
$stats[$obj->id]['count']++; // increment count
}
$obj = $element->to->data is again an array. If I understand your question correctly, you would want to loop through $element->to->data as well. So your code now becomes:
$stats = array();
foreach ($dataArray as $element) {
$toArray = $element->to->data[0];
foreach($toArray as $toElement) {
// check if the key was already created or not
if(isset($stats[$toElement->id])) {
$stats[$toElement->id]['count']++;
}
else {
$stats[$toElement->id] = array('name'=>$toArray->name,'count'=>1);
}
}
}
Update:
Considering performance benchmarks, isset() is lot more faster than array_key_exists (but it returns false even if the value is null! In that case consider using isset() || array_key exists() together.
Reference: http://php.net/manual/en/function.array-key-exists.php#107786

Ranking within a PHP array

I have a seemingly simple ranking-type problem associated with php arrays, unfortunately after much research it has defeated me:
I have a simple array where the keys are names of people and the values are just associated numbers:
$myArray = Array("David"=>36, "James"=>24, "Sarah"=>70, "Mary"=>55);
Here’s the challenge: Given a name, what is their rank within the array? For example: Sarah=rank1; It seems simple because I figured I could just sort the array by the values then loop though to the required name to get the rank. However, weirdly when I sort the array it just unhelpfully returns 1!
print_r(asort($myArray)) = 1 (??)
I suppose I could put the array in an MySQL table but that seems a bit heavy handed. Is anyone aware of a php solution? Where am I going wrong with the sort? I've read the documentation here and it seems asort is the appropriate function (preserves association and sorts on values).
Thanks
The Grinch
(Edited - works now)
Kind of ugly but this should work:
arsort($origArr);
$rankedArr = array_keys($origArr);
foreach ($rankedArr as $rank => $person) {
if ($person == 'Sarah') {
echo $rank + 1;
break;
}
}
What you're doing is first sorting by values, then you're dropping those values and just getting an indexed list of people. Their key value + 1 is their rank. (because first is 0, right?)
EDIT2 - slightly cleaner:
arsort($origArr);
$rankedArr = array_keys($origArr);
$finalRanks = array_flip($rankedArr);
$rank = $finalRanks['Sarah'] + 1;
:-)
asort function returns a boolean and sort the given array as a reference
var_dump(asort($myArray)) = bool(true)
If you print_r($myArray) after this previous line, you'll get your sorted array in $myArray
EDIT: Re-read.
Try doing this to get your ranking numerously:
<?php
/* asort = Lower num to Upper */
asort($myArray);
/* arsort = Upper one to lower */
// arsort($myArray);
$ranks = array_fill(1,count($myArray),'foo');
$ranked = array_combine(array_flip($myArray),array_keys($ranks));
/* Output */
print_r($ranked);
/* Array ( [James] => 1 [David] => 2 [Mary] => 3 [Sarah] => 4 ) */
?>
asort returns bool value, as described here.

Autofill array with empty data to match another array size

I have 2 sets of arrays:
$dates1 = array('9/12','9/13','9/14','9/15','9/16','9/17');
$data1 = array('5','3','7','7','22','18');
// for this dataset, the value on 9/12 is 5
$dates2 = array('9/14','9/15');
$data2 = array('12','1');
As you can see the 2nd dataset has fewer dates, so I need to "autofill" the reset of the array to match the largest dataset.
$dates2 = array('9/12','9/13','9/14','9/15','9/16','9/17');
$data2 = array('','','12','1','','');
There will be more than 2 datasets, so I would have to find the largest dataset, and run a function for each smaller dataset to properly format it.
The function I'd create is the problem for me. Not even sure where to start at this point. Also, I can format the date and data arrays differently (multidimensional arrays?) if for some reason that is better.
You can do this in a pretty straightforward manner using some array functions. Try something like this:
//make an empty array matching your maximum-sized data set
$empty = array_fill_keys($dates1,'');
//for each array you wish to pad, do this:
//make key/value array
$new = array_combine($dates2,$data2);
//merge, overwriting empty keys with data values
$new = array_merge($empty,$new);
//if you want just the data values again
$data2 = array_values($new);
print_r($data2);
It would be pretty easy to turn that into a function or put it into a for loop to operate on your array sets. Turning them into associative arrays of key/value pairs would make them easier to work with too I would think.
If datas are related will be painful to scatter them on several array.
The best solution would be model an object with obvious property names
and use it with related accessor.
From your question I haven't a lot of hint of what data are and then I have to guess a bit:
I pretend you need to keep a daily log on access on a website with downloads. Instead of using dates/data1/data2 array I would model a data structure similar to this:
$log = array(
array('date'=>'2011-09-12','accessCount'=>7,'downloadCount'=>3),
array('date'=>'2011-09-13','accessCount'=>9), /* better downloadsCount=>0 though */
array('date'=>'2011-09-15','accessCount'=>7,'downloadCount'=>3)
...
)
Using this data structure I would model a dayCollection class with methods add,remove,get,set, search with all methods returning a day instance (yes, the remove too) and according signature. The day Class would have the standard getter/setter for every property (you can resolve to magic methods).
Depending on the amount of data you have to manipulate you can opt to maintain into the collection just the object data (serialize on store/unserialize on retrieve) or the whole object.
It is difficult to show you some code as the question is lacking of details on your data model.
If you still want to pad your array than this code would be a good start:
$temp = array($dates, $data1, $data2);
$max = max(array_map('count',$temp));
$result = array_map( function($x) use($max) {
return array_pad($x,$max,0);
}, $temp);
in $result you have your padded arrays. if you want to substitute your arrays do a simple
list($dates, $data1, $data2) = array_map(....
You should use hashmaps instead of arrays to associate each date to a data.
Then, find the largest one, cycle through its keys with a foreach, and test the existence of the same key in the small one.
If it doesn't exist, create it with an empty value.
EDIT with code (for completeness, other answers seem definitely better):
$dates_data1 = array('9/12'=>'5', '9/13'=>'3', '9/14'=>'7' /* continued */);
$dates_data2 = array('9/14'=>'12', '9/15'=>'1');
#cycle through each key (date) of the longest array
foreach($dates_data1 as $key => $value){
#check if the key exists in the smallest and add '' value if it does not
if(!isset( $date_data2[$key] )){ $date_data2[$key]=''; }
}

Categories