Loop through nested arrays in PHP - php

I have a very complex array that I need to loop through.
Array(
[1] => Array(
[1] => ""
[2] => Array(
[1] => ""
[2] => Array(
[1] => ""
)
)
)
)
I can't use nested loops because this array could contain hundreds of nested arrays. Also, the nested ones could contain nested arrays too.
This array presents comments and replies, Where replies could contain more replies.
Any thoughts?

You could use a \RecursiveArrayIterator, which is part of the PHP SPL, shipped non-optional, with the PHP core.
<?php
$arr = [
'lvl1-A' => [
'lvl2' => [
'lvl3' => 'done'
],
],
'lvl1-B' => 'done',
];
function traverse( \Traversable $it ): void {
while ( $it->valid() ) {
$it->hasChildren()
? print "{$it->key()} => \n" and traverse( $it->getChildren() )
: print "{$it->key()} => {$it->current()}\n";
$it->next();
}
}
$it = new \RecursiveArrayIterator( $arr );
$it->rewind();
traverse( $it );
print 'Done.';
Run and play this example in the REPL here: https://3v4l.org/cGtoi
The code is just meant to verbosely explain what you can expect to see. The Iterator walks each level. How you actually code it is up to you. Keep in mind that filtering or flattening the array (read: transforming it up front) might be another option. You could as well use a generator and emit each level and maybe go with Cooperative Multitasking/ Coroutines as PHP core maintainer nikic explained in his blog post.
ProTip: Monitor your RAM consumption with different variants in case your nested Array really is large and maybe requested often or should deliver results fast.
In case you really need to be fast, consider streaming the result, so you can process the output while you are still working on processing the input array.
A last option might be to split the actual array in chunks (like when you are streaming them), therefore processing smaller parts.

The case is quite complex, as you have to loop, but you can't or don't want to for some reasons:
... that I need to loop through
and
I can't use nested loops because this array could contain hundreds of nested arrays
It means you have to either handle your data differently, as you can pack that huge amount of data to be processed later.
If for some reasons it's not an option, you can consider to:
split somehow this big array into smaller arrays
check how does it work with json_encode and parsing string with str_* functions and regex
Your question contains too many things we can't be sure e.g. what exactly these subarrays contain, can you ignore some parts of them, can you change the code that creates huge array in first place etc.
Assuming on the other hand that you could loop. What could bother you? The memory usage, how long it will take etc.?
You can always use cron to run it daily etc. but the most important is to find the cause why you ended up with huge array in the first place.

Related

Memory effective Linear interpolation with PHP

I have a 1D array (XYData), e.g.
$TE = array(
"1"=>"20",
"2"=>"30",
"5"=>"50",
"10"=>"90"
)
I would like to create a memory effective PHP function which do the Linear interpolation of the passed X value and return the corresponding Y value. e.g.
calling function interpolate($TE,9.5)
then it should return 86
Is there any way to avoid the array search as the XYData set may be very long, say more then 100 points.
Thank you in advance!
No, you cannot avoid looking at your array. To make it more efficient you have to restructure your data. Do this by recursively looking for the middle, and then split it at that point into two parts. For your short example you would get this:
$TER = array("2 and lower" => array("1" => "20",
"2" => "30"),
"5 and higher" => array("5" => "50",
"8" => "100"));
No recursion is shown, and it really doesn't make any sense for such a small set of data, but when the dataset becomes large there's a clear advantage. It's basically a simple binary search tree.
But I have my doubts implementing it would be useful in this case. I'm not going to work it all out, you really should have 100.000 items or more to make this useful. If not, then just work through the array.

How a hash or mapping works in PHP

In the language of Perl, I define a hash as a mapping between one thing and another or an essential list of elements. As stated in the documentation..
A hash is a basic data type. It uses keys to access its contents.
So basically a hash is close to an array. Their initializations even look very similar.
If I were to create a mapping in Perl, I could do something like below for comparing.
my %map = (
A => [qw(a b c d)],
B => [qw(c d f a)],
C => [qw(b d a e)],
);
my #keys = keys %map;
my %matches;
for my $k ( 1 .. #keys ) {
$matches{$_} |= 2**$k for #{$map{ $keys[$k-1] }};
}
for ( sort keys %matches ) {
my #found;
for my $k ( 1 .. #keys ) {
push #found, $keys[$k-1] if $matches{$_} & 2**$k;
}
print "$_ found in ", (#found? join(',', #found) : 0 ), "\n";
}
Output:
a found in A,C,B
b found in A,C
c found in A,B
d found in A,C,B
e found in C
f found in B
I would like to find out the best method of doing this for performance and efficiency in php
If I understand correctly, you are looking to apply your knowledge of Perl hashes to PHP. If I'm correct, then...
In PHP a "Perl hash" is generally called an "associative array", and PHP implements this as an array that happens to have keys as indexes and its values are just like a regular array. Check out the PHP Array docs for lots of examples about how PHP lets you work with arrays of this (and other) types.
The nice thing about PHP is it is very flexible as to how you can deal with arrays. You can define an array as having key-value pairs then treat it like a regular array and ignore the keys, and that works just fine. You can mix and match...it doesn't complain much.
Philosophically, a hash or map is just a way to keep discrete pieces of related information together. That's all most non-primitive data structures are, and PHP is not very opinionated about how you go about things; it has lots of built-in optimizations, and does a pretty solid job of doing these types of things efficiently.
To answer your questions related to your example:
1) As for simplicity (I think you mean) and maintainability, I don't think there's anything wrong with your use of an associative array. If a data set is in pairs, then key-value pairs is a natural way to express this type of data.
2) As for most efficient, as far as lines of code and script execution overhead goes...well, the use of such a mapping is a vanishingly small task for PHP. I don't think any other way of handling it would matter much, PHP can handle it by the thousands without complaint. Now if you could avoid the use of a regular expression, on the other hand...
3) You're using it, really. Don't over think it - in PHP this is just an "array", and that's it. It's a variable that holds an arbitrary amount of elements, and PHP handles multiple-dimensions or associativity pretty darn well. Well enough that it's almost never going to be the cause of any problem you have.
PHP will handle things like hash/maps behind the scenes very logically and efficiently, to the point that part of the whole point of the language is for you not to bother to try to think about such things. If you have relates pieces of data in chunks, use an array; if the pieces of data comes in pairs, use key-value pairs; if it comes by the dozen, use an "array of arrays" (a multidimensional array where some - or all - of it's elements are arrays).
PHP doesn't do anything stupid like create a massive overhead just because you wanted to use key-value pairs, and it has lots of built-in features like foreach $yourArray as $key => $value and the functions you used like array_keys() and array_values(). Feel free to use them - as core features they are generally pretty darn well optimized!
For what you are doing I would rather use sprintf:
$format = 'Hello %s how are you. Hey %s, hi %s!';
printf($format, 'foo', 'bar', 'baz');

Save Indexed Array

I am currently trying to figure out how to save a indexed array to a field in my database. That said, I know an array can't be saved to a database, but you can serialize it or implode it and then save. Im not sure which one I should be using though. I don't want a collection of items to be saved in just one cell. I need the list of items to be saved one by one in the column. So my question is do I need to be using the serialize method, implode or something else? Here is a glimpse of my code and the array I am trying to save.
public function findPolicyIds($coverageId = null) {
$policyid = $this->Policy->find('all', array(
'recursive' => -1,
'conditions' => array('Policy.coverage_id' => $coverageId),
'fields' => array('Policy.id')));
foreach($policyid as $id) {
$all[] = $id['Policy']['id'];
}
return $all;
}
Array
(
[0] => 5202834f-111c-4a76-8b33-1ed8ae78509d
[1] => 5202834f-2ba8-4957-91db-1ed8ae78509d
[2] => 5202834f-356c-49a1-beeb-1ed8ae78509d
[3] => 5202834f-3b40-453f-a491-1ed8ae78509d
It depends.
This is probably the best answer to give you.
Do whatever you like, as long as it doesn't magically corrupt the data between write and read.
Lemme take a moment to explore a few options on your behalf.
var_export() - might not be the best idea, securitywise
serialize() - seems straightforward
implode() - seems straightforward
json_encode() - seems straightforward
There are probably other, more obscure, options available. You could even build up complex data sets with XML, if you like.
Alternatively, why not normalize the schema and add a new table to present that collections of array-data ? Normalization tends to save your bacon in the future.

Flatten recursive folder structure in PHP for mysql dbase

Perhaps there's a better method than the PHP route, I am open to ideas.
The problem: I have a folder structure for each user. This folder may contain files or sub-folders. I am only concerned with the folders which have files. I am able to run a query and get a recursive listing on a multidimensional array (from PHP code posted on the web). The issue is that I get a multidimensional array that when flattened only lists the inner most file name, and I need to store the path and file -or- the path+file in my sql dbase. The idea is to allow the user to view his files and delete them using a web interface.
Here is an example result from the PHP recursive function. Inside a folder named "Jimmy", you find:
Array
(
[0] => info.txt
[1] => log.tmp
[2] => README.md
[css] => Array
(
[0] => style.css
)
[images] => Array
(
[0] => flower.gif
)
[3] => index.php
[testDir] => Array
(
[anotherTestDir] => Array
(
[0] => test2.php
)
)
[5] => listing.txt
[temp] => Array
(
)
)
What I'd wold like to see is this:
/jimmy/info.txt
/jimmy/log.tmp
/jimmy/README.md
/jimmy/css/style.css
/jimmy/images/flower.gif
/jimmy/index.php
/jimmy/testDir/anotherTestDir/test2.php
/jimmy/listing.txt
And then I would need to sort the string and add it to mysql dbase. Any help on how to achieve this effect would be much appreciated. Once I have my flatten array i want to add it in one go to the user's "folder" table.
<?php
$d = new RecursiveIteratorIterator(new RecursiveDirectoryIterator('/path/to/dir'));
foreach($d as $file){
if($file->isFile()) echo $d->getSubPathname().PHP_EOL;
}
If you actually read the question, these "files" are not on a local filesystem (or, any filesystem) but in a virtual tree stored in a database, so the Iterator example is not directly useful in this case.
You could however use that example if you write a file stream wrapper for your database structure. But even then you'd be stuck on a tough recursive SQL query (to make your database server 'build' you the strings in a computed field, which is what I came here hoping to find...), or lots of CPU time in PHP collating through the structure to generate the "long name" of each file or folder.
However if you just want to work with the data as input, you could use a loop and some is_numeric() or is_string() logic (though you can't have a directory that is numeric then, which may or may not be an issue)... or detect the leafs by the fact they have no array subchildren and then you know that's a 'file' and the parents are all 'folders' regardless of the array index "data type". For recursing arrays like that I usually use by-reference loops, especially if I will be modifying nodes (or adding elements within the tree such as a 'full_path' field for each node). There are some good examples of this on the PHP doc site for the page about references and dereferencing. Also if your data set will never be large this may work acceptably. In my project it won't, we've already hit some resource limits and it's taking too long to process within each 'hit' to our file manager interface. So I strongly urge making the database do the heavy lifting and string concatenations (and then tell me how you did it!)

Why does the sort order of multidimensional child arrays revert as soon as foreach loop used for sorting ends?

I have a very strange array sorting related problem in PHP that is driving me completely crazy. I have googled for hours, and still NOTHING indicates that other people have this problem, or that this should happen to begin with, so a solution to this mystery would be GREATLY appreciated!
To describe the problem/question in as few words as possible: When sorting an array based on values inside a multiple levels deeply nested array, using a foreach loop, the resulting array sort order reverts as soon as execution leaves the loop, even though it works fine inside the loop. Why is this, and how do I work around it?
Here is sample code for my problem, which should hopefully be a little more clear than the sentence above:
$top_level_array = array('key_1' => array('sub_array' => array('sub_sub_array_1' => array(1),
'sub_sub_array_2' => array(3),
'sub_sub_array_3' => array(2)
)
)
);
function mycmp($arr_1, $arr_2)
{
if ($arr_1[0] == $arr_2[0])
{
return 0;
}
return ($arr_1[0] < $arr_2[0]) ? -1 : 1;
}
foreach($top_level_array as $current_top_level_member)
{
//This loop will only have one iteration, but never mind that...
print("Inside loop before sort operation:\n\n");
print_r($current_top_level_member['sub_array']);
uasort($current_top_level_member['sub_array'], 'mycmp');
print("\nInside loop after sort operation:\n\n");
print_r($current_top_level_member['sub_array']);
}
print("\nOutside of loop (i.e. after all sort operations finished):\n\n");
print_r($top_level_array);
The output of this is as follows:
Inside loop before sort operation:
Array
(
[sub_sub_array_1] => Array
(
[0] => 1
)
[sub_sub_array_2] => Array
(
[0] => 3
)
[sub_sub_array_3] => Array
(
[0] => 2
)
)
Inside loop after sort operation:
Array
(
[sub_sub_array_1] => Array
(
[0] => 1
)
[sub_sub_array_3] => Array
(
[0] => 2
)
[sub_sub_array_2] => Array
(
[0] => 3
)
)
Outside of loop (i.e. after all sort operations finished):
Array
(
[key_1] => Array
(
[sub_array] => Array
(
[sub_sub_array_1] => Array
(
[0] => 1
)
[sub_sub_array_2] => Array
(
[0] => 3
)
[sub_sub_array_3] => Array
(
[0] => 2
)
)
)
)
As you can see, the sort order is "wrong" (i.e. not ordered by the desired value in the innermost array) before the sort operation inside the loop (as expected), then is becomes "correct" after the sort operation inside the loop (as expected).
So far so good.
But THEN, once we're outside the loop again, all of a sudden the order has reverted to its original state, as if the sort loop didn't execute at all?!?
How come this happens, and how will I ever be able to sort this array in the desired way then?
I was under the impression that neither foreach loops nor the uasort() function operated on separate instances of the items in question (but rather on references, i.e. in place), but the result above seems to indicate otherwise? And if so, how will I ever be able to perform the desired sort operation?
(and WHY doesn't anyone else than me on the entire internet seem to have this problem?)
PS.
Never mind the reason behind the design of the strange array to be sorted in this example, it is of course only a simplified PoC of a real problem in much more complex code.
Your problem is a misunderstanding of how PHP provides your "value" in the foreach construct.
foreach($top_level_array as $current_top_level_member)
The variable $current_top_level_member is a copy of the value in the array, not a reference to inside the $top_level_array. Therefore all your work happens on the copy and is discarded after the loop completes. (Actually it is in the $current_top_level_member variable, but $top_level_array never sees the changes.)
You want a reference instead:
foreach($top_level_array as $key => $value)
{
$current_top_level_member =& $top_level_array[$key];
EDIT:
You can also use the foreach by reference notation (hat tip to air4x) to avoid the extra assignment. Note that if you are working with an array of Objects, they are already passed by reference.
foreach($top_level_array as &$current_top_level_member)
To answer you question as to why PHP defaults to a copy instead of a reference, it's simply because of the rules of the language. Scalar values and arrays are assigned by value, unless the & prefix is used, and objects are always assigned by reference (as of PHP 5). And that is likely due to a general consensus that it's generally better to work with copies of everything expect objects. BUT--it is not slow like you might expect. PHP uses a lazy copy called copy on write, where it is really a read-only reference. On the first write, the copy is made.
PHP uses a lazy-copy mechanism (also called copy-on-write) that does
not actually create a copy of a variable until it is modified.
Source: http://www.thedeveloperday.com/php-lazy-copy/
You can add & before $current_top_level_member and use it as reference to the variable in the original array. Then you would be making changes to the original array.
foreach ($top_level_array as &$current_top_level_member) {

Categories