PHP How do I calculate level of nested calls in recursive function? - php

I have a recursive function in php which gets from a database a folder tree. Each folder has an id, a name and a parent id.
function show_subfolders($parent=0, $indent=0) {
$indent++;
$folders = sql_to_assoc("SELECT * FROM `folders` WHERE 'parent' = ".$parent.";");
foreach($folders as $folder) {
echo ' '.$folder['naam'].' <br>';
show_subfolders($folder['id'], $indent);
}
}
show_subfolders();
I expect that the variable $indent tells us the level of nestedness of the recursive function, but it is not.. it just counts the number of calls. I hope it is clear that I want to know the 'generation' of each child-element.

Try taking the $indent var outside of the function scope, also, after you end traversing a node(folder) contents, you are going back up a level so at some point you should do a $indent--;
$indent = 0;
function show_subfolders(){
// give this function access to $indent
//you could also use a class var $this->indent if you make this into a class method
global $indent;
$folders = sql_to_assoc("SELECT * FROM `folders` WHERE 'parent' = ".$parent.";");
foreach($folders as $folder) {
echo str_repeat (' ', $indent).' '.$folder['naam'].' <br>';
$indent++;
show_subfolders($folder['id']);
$indent--;
}
}
Also added the str_repeat function so that your links are 'indented' when rendered in the browser. Although a better approach would be to draw the links in a which will allow you to control the visual indentation with css. That would make it:
$indent = 0;
function show_subfolders(){
// give this function access to $indent
//you could also use a class var $this->indent if you make this into a class method
global $indent;
$folders = sql_to_assoc("SELECT * FROM `folders` WHERE 'parent' = ".$parent.";");
if (count($folders)){
echo '<ul>';
foreach($folders as $folder) {
echo '<li> '.$folder['naam'].' </li>';
$indent++;
show_subfolders($folder['id']);
$indent--;
}
echo '</ul>';
}
}

Related

Restricting the number of times a function recurses in php codeigniter

I want to generate a multilevel hierarchical select option to select parent or any of its child categories in codeigniter. I want to limit the hierarchy level to 3. i.e the option must show parent, its first level child and its second level child with child category indented to the right of parent. I've no problem going through all the hierarchy level. But I can't generate the select option upto a particular hierarchy level. Three levels of hierarchy is what I need in my case. Can anyone please help me.
Here is what I tried so far:
<?php
$category = $this->get_category();
function get_category() {
$this->db->order_by('parent_id', 'DESC');
$this->db->select('id, name, parent_id');
return $this->db->get('tbl_job_category')->result_array();
}
/*
*$category contains the following json data obtained from DB
$category = [{"id":"20","name":"MVC","parent_id":"16"},
{"id":"16","name":"Spring","parent_id":"14"},
{"id":"12","name":"Car Driver","parent_id":"13"},
{"id":"6","name":"Electrical","parent_id":"5"},
{"id":"3","name":"Civil","parent_id":"5"},
{"id":"14","name":"java","parent_id":"2"},
{"id":"15","name":"javascript","parent_id":"2"},
{"id":"17","name":"Computer Operator","parent_id":"1"},
{"id":"2","name":"Programming","parent_id":"1"},
{"id":"4","name":"Networking","parent_id":"1"},
{"id":"11","name":"Hardware","parent_id":"1"},
{"id":"13","name":"Driver","parent_id":"0"},
{"id":"5","name":"Engineering","parent_id":"0"},
{"id":"19","name":"Economics","parent_id":"0"},
{"id":"1","name":"Information Technology","parent_id":"0"}];
*/
$category_options = $this->multilevel_select($category);
function multilevel_select($array,$parent_id = 0,$parents = array()) {
static $i=0;
if($parent_id==0)
{
foreach ($array as $element) {
if (($element['parent_id'] != 0) && !in_array($element['parent_id'],$parents)) {
$parents[] = $element['parent_id'];
}
}
}
$menu_html = '';
foreach($array as $element){
if($element['parent_id']==$parent_id){
$menu_html .= '<option>';
for($j=0; $j<$i; $j++) {
$menu_html .= '—';
}
$menu_html .= $element['name'].'</option>';
if(in_array($element['id'], $parents)){
$i++;
$menu_html .= $this->multilevel_select($array, $element['id'], $parents);
}
}
}
$i--;
return $menu_html;
}
echo $category_options;
Limiting the recursion involves three steps :
Initializing a counter variable passed as parameter to the recursive function.
Testing the counter value against the desired boundary prior to recursing
Passing an incremented counter value in case of recursive call.
In your case that would be the $level variable below :
function multilevel_select($array,$parent_id = 0,$parents = array(), $level=0) {
// ....
if(in_array($element['id'], $parents) && $level < 2){ // your boundary here, 2 for third nesting level from root
$i++;
$menu_html .= $this->multilevel_select($array, $element['id'], $parents, $level+1);
}
}
}
$i--;
return $menu_html;
}

How to keep memory low when using a hierarchical object structure

I have a simple object thing that is able to have children of the same type.
This object has a toHTML method, which does something like:
$html = '<div>' . $this->name . '</div>';
$html .= '<ul>';
foreach($this->children as $child)
$html .= '<li>' . $child->toHTML() . '</li>';
$html .= '</ul>';
return $html;
The problem is that when the object is complex, like lots of children with children with children etc, memory usage skyrockets.
If I simply print_r the multidimensional array that feeds this object I get like 1 MB memory usage, but after I convert the array to my object and do print $root->toHtml() it takes 10 MB !!
How can I fix this?
====================================
Made a simple class that is similar to my real code (but smaller):
class obj{
protected $name;
protected $children = array();
public function __construct($name){
$this->name = $name;
}
public static function build($name, $array = array()){
$obj = new self($name);
if(is_array($array)){
foreach($array as $k => $v)
$obj->addChild(self::build($k, $v));
}
return $obj;
}
public function addChild(self $child){
$this->children[] = $child;
}
public function toHTML(){
$html = '<div>' . $this->name . '</div>';
$html .= '<ul>';
foreach($this->children as $child)
$html .= '<li>' . $child->toHTML() . '</li>';
$html .= '</ul>';
return $html;
}
}
And tests:
$big = array_fill(0, 500, true);
$big[5] = array_fill(0, 200, $big);
print_r($big);
// memory_get_peak_usage() shows 0.61 MB
$root = obj::build('root', $big);
// memory_get_peak_usage() shows 18.5 MB wtf lol
print $root->toHTML();
// memory_get_peak_usage() shows 24.6 MB
The problem is that you're buffering all the data in memory, which you don't actually need to do, as you're just outputting the data, rather than actually processing it.
Rather than buffering everything in memory, if all you want to do is output it you should just output it to wherever it's going to:
public function toHTMLOutput($outputStream){
fwrite($outputStream, '<div>' . $this->name . '</div>';
fwrite($outputStream, '<ul>');
foreach($this->children as $child){
fwrite($outputStream, '<li>');
$child->toHTMLOutput($outputStream);
fwrite($outputStream, '</li>');}
}
fwrite($outputStream, '</ul>');
}
$stdout = fopen('php://stdout', 'w');
print $root->toHTMLOutput($stdout);
or if you want to save the output to a file
$stdout = fopen('htmloutput.html', 'w');
print $root->toHTMLOutput($stdout);
Obviously I've only implemented it for the toHTML() function but the same principle should be done for the build function, which could lead to you skipping a separate toHTML function at all.
Introduction
Since you are sill going to output the HTML there is no need to save it indirectly consuming memory.
Here is a simple class that :
Builds menu from multidimensional array
Memory efficient uses Iterator
Can Write to Socket , Stream , File , array , Iterator etc
Example
$it = new ListBuilder(new RecursiveArrayIterator($big));
// Use Echo
$m = memory_get_peak_usage();
$it->display();
printf("%0.5fMB\n", (memory_get_peak_usage() - $m) / (1024 * 1024));
Output
0.03674MB
Other Output Interfaces
$big = array_fill(0, 500, true);
$big[5] = array_fill(0, 200, $big);
Simple Compare
// Use Echo
$m = memory_get_peak_usage();
$it->display();
$responce['echo'] = sprintf("%0.5fMB\n", (memory_get_peak_usage() - $m) / (1024 * 1024));
// Output to Stream or File eg ( Socket or HTML file)
$m = memory_get_peak_usage();
$it->display(fopen("php://output", "w"));
$responce['stream'] = sprintf("%0.5fMB\n", (memory_get_peak_usage() - $m) / (1024 * 1024));
// Output to ArrayIterator
$m = memory_get_peak_usage();
$it->display($array = new ArrayIterator());
$responce['iterator'] = sprintf("%0.5fMB\n", (memory_get_peak_usage() - $m) / (1024 * 1024));
// Output to Array
$m = memory_get_peak_usage();
$it->display($array = []);
$responce['array'] = sprintf("%0.5fMB\n", (memory_get_peak_usage() - $m) / (1024 * 1024));
echo "\n\nResults \n";
echo json_encode($responce, 128);
Output
Results
{
"echo": "0.03684MB\n",
"stream": "0.00081MB\n",
"iterator": "32.04364MB\n",
"array": "0.00253MB\n"
}
Class Used
class ListBuilder extends RecursiveIteratorIterator {
protected $pad = "\t";
protected $o;
public function beginChildren() {
$this->output("%s<ul>\n", $this->getPad());
}
public function endChildren() {
$this->output("%s</ul>\n", $this->getPad());
}
public function current() {
$this->output("%s<li>%s</li>\n", $this->getPad(1), parent::current());
return parent::current();
}
public function getPad($n = 0) {
return str_repeat($this->pad, $this->getDepth() + $n);
}
function output() {
$args = func_get_args();
$format = array_shift($args);
$var = vsprintf($format, $args);
switch (true) {
case $this->o instanceof ArrayIterator :
$this->o->append($var);
break;
case is_array($this->o) || $this->o instanceof ArrayObject :
$this->o[] = $var;
break;
case is_resource($this->o) && (get_resource_type($this->o) === "file" || get_resource_type($this->o) === "stream") :
fwrite($this->o, $var);
break;
default :
echo $var;
break;
}
}
function display($output = null) {
$this->o = $output;
$this->output("%s<ul>\n", $this->getPad());
foreach($this as $v) {
}
$this->output("%s</ul>\n", $this->getPad());
}
}
Conclusion
As you can see looping with iterator is fast but store values in iterator or object might not be that memory efficient.
Total number of elements in Your array is a little over 100000.
Each element of Your array is just one byte (boolean) so for over 100000 elements it takes 100000bytes ~0.1MB
Each of Your objects is ~100 bytes it is 100*100000 = 100000000 bytes ~ 10MB
But You have ~18MB so where is this 8 from?
If You run this code
<?php
$c = 0; //we use this to count object isntances
class obj{
protected $name;
protected $children = array();
public static $c=0;
public function __construct($name){
global $c;
$c++;
$this->name = $name;
}
public static function build($name, $array = array()){
global $c;
$b = memory_get_usage();
$obj = new self($name);
$diff = memory_get_usage()-$b;
echo $c . ' diff ' . $diff . '<br />'; //display change in allocated size
if(is_array($array)){
foreach($array as $k => $v)
$obj->addChild(self::build($k, $v));
}
return $obj;
}
public function addChild(self $child){
$this->children[] = $child;
}
public function toHTML(){
$html = '<div>' . $this->name . '</div>';
$html .= '<ul>';
foreach($this->children as $child)
$html .= '<li>' . $child->toHTML() . '</li>';
$html .= '</ul>';
return $html;
}
}
$big = array_fill(0, 500, true);
$big[5] = array_fill(0, 200, $big);
$root = obj::build('root', $big);
You will notice a change is constant with exception for objects created as
1024th, 2048th, 4096th...
I don't have link to any article or manual page about it but my guess is that php hold references to each created object in array with initial size of 1024. When You make this array full its size will get doubled to make space for new objects.
If You take difference from for example 2048th object subtract a size of object( the constant value You have in other lines) and divide by 2048 You will always get 32 - standard size of pointer in C.
So for 100000 objects this array grown to size of 131072 elements.
131072*32 = 4194304B = 4MB
This calculation are just approximate but I think it answers Your question what takes so much memory.
To answer how to keep memory low - avoid using objects for large set of data.
Obviously objects are nice and stuff but primitive data types are faster and smaller.
Maybe You can make it work with one object containing array with data. Hard to propose any alternative without more info about this objects and what methods/interface they require.
One thing that might be catching you is that you might be getting close to blowing your stack because of recursion. It might make sense in this case to create a rendering function that deals with the tree as a whole to render instead of relying on recursion to do the rendering for you. For informative topics on this see tail call recursion and tail call optimization.
To stick with your code's current structure and dodge a lot of the resource problems that you are likely facing the simplest solution may be to simply pass in the html string as a reference like:
class obj{
protected $name;
protected $children = array();
public function __construct($name){
$this->name = $name;
}
public static function build($name, $array = array()){
$obj = new self($name);
if(is_array($array)){
foreach($array as $k => $v)
$obj->addChild(self::build($k, $v));
}
return $obj;
}
public function addChild(self $child){
$this->children[] = $child;
}
public function toHTML(&$html = ""){
$html .= '<div>' . $this->name . '</div>';
$html .= '<ul>';
foreach($this->children as $child){
$html .= '<li>';
$html .= $child->toHTML($html);
$html .= '</li>';
}
$html .= '</ul>';
}
}
This will keep you from hauling around a bunch of duplicate partial tree renders while the recursive calls are resolving.
As for the actual build of the tree I think a lot of the memory usage is just the price of playing with data that big, your options there are either render instead of building up a hierarchical model just to render (just render output instead of building a tree) or, to employ some sort of caching strategies to either cache copies of the object tree or copies of the rendered html depending on how the data is used within your site. If you have control of the inbound data invalidating relevant cache keys can be added to that work flow to keep the cache from getting stale.

How to fill an array with a function, and use that array outside the function?

I'am working in CodeIgniter (CI), and trying to create a nested set of category items for a dropdown list. To create a dropdown box, in CI you need to echo form_dropdown('name', $array, $selectedID).
Here is my function to create a nested list array:
$categoryData = array();
function list_categories($cats, $sub = ''){
foreach($cats as $cat){
//$cat['category']->id = $sub.$cat['category']->title;
$categoryData[$cat['category']->id] = $sub.$cat['category']->title;
if( sizeof($cat['children']) > 0 ){
$sub2 = str_replace('—→ ', '–', $sub);
$sub2.= '–→ ';
list_categories($cat['children'], $sub2);
}
}
}
If I will do a var_dump($categoryData); just right after the foreach inside the list_categories() function, it will return the array of nested sets. So this is ok when using var_dump() inside the function. But I need to do this:
<?php
list_categories($categories);
var_dump($categoryData);
?>
And here i get an empty array, here is an output:
array (size=0)
empty
Could someone tell me what I'am doing wrong here ?
Your function modifies local copy, which should be returned to global scope. What you want to achieve might be done with globals ("bad practice"), return or references.
Try to use references:
function list_categories(&$result, $cats, $sub = ''){ // <- THIS
foreach($cats as $cat){
//$cat['category']->id = $sub.$cat['category']->title;
$result[$cat['category']->id] = $sub.$cat['category']->title; // <- THIS
if( sizeof($cat['children']) > 0 ){
$sub2 = str_replace('—→ ', '–', $sub);
$sub2.= '–→ ';
list_categories($result, $cat['children'], $sub2); // <- THIS
}
}
}
$categoryData = array();
list_categories($categoryData, $categories); // <- THIS
UPD: In the end, for recusive function, references are better (as for me). Sorry for the inconvinience.
you should:
function list_categories($cats, $sub = ''){
global $categoryData; // add this
If you do not, function does not see the global $categoryData and creates a local one instead. which it does not return.
Note that, minimal use of global variables is recommended for avoiding spagetthi code.

How do I pick only the first few items in an array?

Here's something simple for someone to answer for me. I've tried searching but I don't know what I'm looking for really.
I have an array from a JSON string, in PHP, of cast and crew members for a movie.
Here I am pulling out only the people with the job name 'Actor'
foreach ($movies[0]->cast as $cast) {
if ($cast->job == 'Actor') {
echo '<p>' . $cast->name . ' - ' . $cast->character . '</p>';
}
}
The problem is, I would like to be able to limit how many people with the job name 'Actor' are pulled out. Say, the first 3.
So how would I pick only the first 3 of these people from this array?
OK - this is a bit of over-kill for this problem, but perhaps it serves some educational purposes. PHP comes with a set of iterators that may be used to abstract iteration over a given set of items.
class ActorIterator extends FilterIterator {
public function accept() {
return $this->current()->job == 'Actor';
}
}
$maxCount = 3;
$actors = new LimitIterator(
new ActorIterator(
new ArrayIterator($movies[0]->cast)
),
0,
$maxCount
);
foreach ($actors as $actor) {
echo /*... */;
}
By extending the abstract class FilterIterator we are able to define a filter that returns only the actors from the given list. LimitIterator allows you to limit the iteration to a given set and the ArrayIterator is a simple helper to make native arrays compatible with the Iterator interface. Iterators allow the developer to build chains that define the iteration process which makes them extremely flexible and powerful.
As I said in the introduction: the given problem can be solved easily without this Iterator stuff, but it provides the developer with some extended options and enables code-reuse. You could, for example, enhance the ActorIterator to some CastIterator that allows you to pass the cast type to filter for in the constructor.
Use a variable called $num_actors to track how many you've already counted, and break out of the loop once you get to 3.
$num_actors = 0;
foreach ( $movies[0]->cast as $cast ) {
if ( $cast->job == 'Actor' ) {
echo '...';
$num_actors += 1;
if ( $num_actors == 3 )
break;
}
}
$actors=array_filter($movies[0]->cast, function ($v) {
return $v->job == 'Actor';
});
$first3=array_slice($actors, 0, 3);
or even
$limit=3;
$actors=array_filter($movies[0]->cast, function ($v) use (&$limit) {
if ($limit>0 && $v->job == 'Actor') {
$limit--;
return true;
}
return false;
});
Add a counter and an if statement.
$count = 0;
foreach ($movies[0]->cast as $cast)
{
if ($cast->job == 'Actor')
{
echo '<p>' . $cast->name . ' - ' . $cast-character . '</p>';
if($count++ >= 3)
break;
}
}
$limit = 3;
$count = 0;
foreach ($movies[0]->cast as $cast) {
// You can move the code up here if all you're getting is Actors
if ($cast->job == 'Actor') {
if ($count == $limit) break;// stop the loop
if ($count == $limit) continue;// OR move to next item in loop
$count++;
echo '<p><a href="people.php?id='
. $cast->id
. '">'
. $cast->name
. ' - '
. $cast->character
. '</a></p>';
}
}

Sorting files per directory using SPL's DirectoryTreeIterator

I found a couple of questions (this one and this question) related to the SPL iterators, but I'm not sure if they're helpful in my case, as I'm using a rather high level extension of the RecursiveIteratorIterator; the DirectoryTreeIterator.
Could somebody perhaps show me how to alter the DirectoryTreeIterator or how to sort the returned array per directory after it has been outputted by the iterator?
A method of sorting the files correctly directly on the Apache server is also an option for me, if it's possible using .htaccess for example.
This is the code of DirectoryTreeIterator from the SPL:
/** #file directorytreeiterator.inc
* #ingroup Examples
* #brief class DirectoryTreeIterator
* #author Marcus Boerger
* #date 2003 - 2005
*
* SPL - Standard PHP Library
*/
/** #ingroup Examples
* #brief DirectoryIterator to generate ASCII graphic directory trees
* #author Marcus Boerger
* #version 1.1
*/
class DirectoryTreeIterator extends RecursiveIteratorIterator
{
/** Construct from a path.
* #param $path directory to iterate
*/
function __construct($path) {
parent::__construct(
new RecursiveCachingIterator(
new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::KEY_AS_FILENAME
),
CachingIterator::CALL_TOSTRING|CachingIterator::CATCH_GET_CHILD
),
parent::SELF_FIRST
);
}
/** #return the current element prefixed with ASCII graphics
*/
function current() {
$tree = '';
for ($l=0; $l < $this->getDepth(); $l++) {
$tree .= $this->getSubIterator($l)->hasNext() ? ' ' : ' ';
}
return $tree . ($this->getSubIterator($l)->hasNext() ? ' ' : ' ')
. $this->getSubIterator($l)->__toString();
}
/** Aggregates the inner iterator
*/
function __call($func, $params) {
return call_user_func_array(array($this->getSubIterator(), $func), $params);;
}
}
To clarify why I'm using the code above is because it fits my needs exactly. I want to generate a recursive directory tree prefixed by whitespaces - the original code example by Marcus Boerger adds some ASCI elements. The problem is I don't have control over the sorting of files and directories, so I would like the directory tree to appear like this:
dir001
subdir001
subdir002
subfile001.jpg
file001.png
file002.png
file003.png
dir002
apple.txt
bear.txt
contact.txt
dir003
[...]
Instead, the listings returned by the iterator isn't sorted at all and it shows me something like this:
dir002
bear.txt
apple.txt
contact.txt
dir001
subdir001
subdir002
subfile001.jpg
file002.png
file001.png
file003.png
dir003
[...]
So I guess the solution I'm looking for is some way to call a sort method every time a subdirectory is indexed and added to the directory tree.
I hope I've made it somewhat clearer, as a nonnative speaker it's sometimes hard to put thoughts into coherent sentences (or even words for that matter).
Well, I'm not sure where you got that class from, but it's doing some pretty messed up things (including a few bugs to say the least). And while it uses SPL, it's not an SPL class.
Now, I'm not 100% sure what you mean by "sort", but assuming you're talking about a natural sort, why not just flatten an array, and then sort it?
$it = new RecursiveTreeIterator(
new RecrusiveDirectoryIterator($dir),
RecursiveTreeIterator::BYPASS_KEY,
CachingIterator::CALL_TOSTRING
);
$files = iterator_to_array($it);
natsort($files);
echo implode("\n", $files);
or
$it = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($dir),
RecursiveIteratorIterator::SELF_FIRST
);
$files = iterator_to_array($it);
$files = array_map(function($file) { return (string) $file; }, $files);
natsort($files);
echo implode("\n", $files);
Edit: Based on your edit, here's how I would solve it:
function BuildTree($it, $separator = ' ', $level = '') {
$results = array();
foreach ($it as $file) {
if (in_array($file->getBasename(), array('.', '..'))) {
continue;
}
$tmp = $level . $file->getBaseName();
if ($it->hasChildren()) {
$newLevel = $level . $separator;
$tmp .= "\n" . BuildTree($it->getChildren(), $separator, $newLevel);
}
$results[] = $tmp;
}
natsort($results);
return implode("\n", $results);
};
$it = new RecursiveDirectoryIterator($dir);
$tree = BuildTree($it);
It's a pretty simple recursive parser, and does the natural sort on each level.
Don't know about SPL iterators, but for your iterator you should put the items in an array, then sort them and add them to $tree. I modified the function current but didn't test it:
function current()
{
$tree = '';
$treeitems = array();
for ($l=0; $l < $this->getDepth(); $l++) {
//NOTE: On this line I think you have an error in your original code:
// This ? ' ' : ' ' is strange
$treeitems[] = $this->getSubIterator($l)->hasNext() ? ' ' : ' ';
}
$treeitems.sort();
for each ($treeitems as $treeitem)
$tree .= $treeitem;
return $tree . ($this->getSubIterator($l)->hasNext() ? ' ' : ' ')
. $this->getSubIterator($l)->__toString();
}

Categories