recursive php. Recursion in PHP

This lesson is about recursion in PHP. You can call another function from a function. The program can call the f1() function, which calls the f2() function, and so on. A function that calls itself is called recursive. This type of recursion is called explicit recursion. If the function f1() calls another function f2(), which in turn calls the function f1(), then these functions are also recursive. This type of recursion is called implicit recursion. Clearly, more complex forms of implicit recursion are possible.

Let's assume that for the solution of some task it is necessary to create a recursive function. In this lesson, we will describe one of the well-known strategies for solving a problem using a recursive function. The process of recursive problem solving is divided into stages. In the first step, a recursive function is called to solve the problem. In this function, the problem is solved for the simplest situation. The simplest situation of this problem is called the basic problem. If the function is used to solve a basic problem, then it returns a solution or a result.

If the function is called to solve a problem more complex than the basic problem, then the function divides the problem into two parts:

  • part 1 that the function can solve;
  • part 2 that the function can't solve.

To use recursion, part 2 should be similar to the initial problem, but relatively smaller or simpler.

Since the task created in Part 2 is similar to the initial task, the function calls a new instance of itself to handle the new task. This action is called a recursive call or recursion step.

Since at each recursion step (with each recursive call) the task is divided into two parts, therefore, the number of these recursion steps can be quite large.

To complete the recursion, the recursive function must form a sequence of simplifying (decreasing) problems that approach the basic problem. When, at some recursion step, the function finds a basic problem, it returns the solution (result) of the basic problem to the previous call (previous recursion step). In this call, in turn, the result is combined with the part that the function can solve. The solution formed in this way returns to the step above, and so on. This generates the last result, which is returned to the starting point of the recursive function call. That is, in order to be able to return, each step of the function must be built in such a way that it contains the reserved word return.

To demonstrate the above idea, we will give an example of using a recursive function.

Example 1. Factorial. The factorial of a non-negative integer n is equal to n*(n-1)*(n-2)*...2*1 and is denoted by n!. It is assumed that 1!=1 and 0!=1. For example, 7!=7*6*5*4*3*2*1=5040.

An iterative (non-recursive) solution for factorial calculation is as follows (factorial1.php file):



Factorial



Factorial calculation



Adadi naturali (n>=0):





";
exit;
}
$f=1;
for($i=$n;$i>=1;$i--)
$f*=$i;
echo "$n!=$f";
?>

Let f(n)=n!, then
f(n)=n!=n*(n-1)!=n*f(n-1),
that is, the recursive formula for calculating the factorial is:
f(n)=n*f(n-1).

Example:
f(5)=5*f(4)=5*4*f(3)=5*4*3*f(2)= 5*4*3*2*f(1)= 5*4*3 *2*1=120.

Based on the recursive formula, we will compose a recursive function for calculating the factorial (factorial2.php file):



Factorial



Factorial calculation



Adadi naturali (n>=0):




if(!isset($_GET["n"]) || ($n = $_GET["n"])=="") (
echo "Enter a natural number!
";
exit;
}
$f=factorial($n);
echo "$n!=$f";

function factorial($i) (
if($i<2) return 1;
$k=$i*factorial($i-1);
return $k;
}

?>

If you enter the number 5 into the program, then to calculate 5! the program works as follows:

When the factorial() function is called for the first time, it is checked whether the number sent to the function is less than 2. If the number received by the function is not less than 2, then the task is larger or more complicated than the basic task, therefore, the task is divided into two parts:

  1. $k=$i* - part 1 that the function can solve;
  2. factorial($n-1) is the part 2 that the function cannot solve.

This action is repeated until the basic problem is received, that is, until factorial(1) is called. If the number is less than 2 (1 or 0), then the factorial() function returns the number 1 ($k=1), that is, the basic problem is solved. If this instance of the factorial() function was previously called by another instance of the factorial() function, then the result is returned to the calling instance of the function. There, the returned result $k is multiplied by the parameter $n passed to the function and assigned to $k. If this instance of the factorial() function was previously called by another instance of the factorial() function, then the result is returned to the calling instance of the function and the described process is repeated. If this instance of the factorial() function was called from the main part of the program, then $k is returned to that part, where the result is printed to the screen and the program terminates.

  • Algorithms
  • Hours of thought and experimentation in the field of building hierarchical lists prompted me to write this article. Initially, the logic was tested on SQL queries, but later I decided to implement it in PHP in order to remove the dependence on the DBMS. Using a simple example, I will show how you can go from the root of the hierarchy to each end element and back, the information is rather for beginners.

    So, the test hierarchy with which we have to work:

    The database has the simplest table on the simplest MSSQL server, we will omit the subtleties of the connection, our goal is to deal with the hierarchy and recursion.

    Let's create a table:

    CREATE TABLE .( IDENTITY(1,1) NOT NULL, -- unique field, auto-incrementing NULL, -- this field points to the element one level up, contains the parent's uid (255) NULL, (50) NULL, -- permissions) ON
    Let's fill in the information:

    The description of the fields is in the comments, a little more about the field access:

    By default in my system, for each new document, inherit, that is, inheritance from the parent. For our experiment, we will write domain groups for some elements. In Group Domain Users my account exists, but in AD Group Secret I'm gone.

    What else do we have. An array containing a list of my domain groups. It is obtained quite simply, Windows authentication is enabled on IIS, everything works transparently, in PHP the login of the user is in the $_SERVER["AUTH_USER"] variable, then we get a list of groups with an LDAP request.

    Now I propose to get the necessary data and go straight to the point:

    $stmt = $PDO->query("SELECT * FROM Test"); $table = $stmt->fetchAll(); //Get the table from the database $groups = LDAP::getGroups("$login"); //Get ActiveDirectory groups

    Task #1

    It is necessary to learn how to work with the hierarchy as a tree and not a list. The nesting level is not known in advance and can be any, therefore there must be a universal tool that allows you to traverse the tree both from top to bottom and in the opposite direction.

    Task #2

    It is necessary to flexibly manage access, that is, to give rights to groups, individual documents, etc., by analogy with the NTFS file system, you can close the rights to the entire folder, but for one document in this folder, cut access - the same should happen and we have.

    Task #3

    It is necessary to hide resources to which they do not have access from users, but most importantly, if you have rights to at least one document somewhere in the depth of a branch closed to it, make visible the elements leading to this document (otherwise how will the user get to it?)

    Here is the basic function itself:

    $array = array(); //output array function recursive($data, $pid = 0, $level = 0)( global $array; foreach ($data as $row) ( //loop through rows if ($row["pid"] == $ pid) ( //Start with the rows whose pid is passed to the function, we have it 0, i.e. the root of the site //Collect the row into an associative array $_row["uid"] = $row["uid"]; $ _row["pid"] = $row["pid"]; $_row["name"] = $_row["name"] = str_pad("", $level*3, ".").$row[" name"]; //Add points using the str_pad function $_row["level"] = $level; //Add a level $array = $_row; //Add each row to the output array //The row has been processed, now run the same function for current uid, i.e. //child row (for which this uid is pid) will be reversed recursive($data, $row["uid"], $level + 1); ) ) ) recursive($table); //Launch
    The description was mostly given in the comments, but to put it simply - after the foreach loop goes through the line and does something with the data (in our case, it simply copies the data to another array, adding the level field and dots to the name), it runs the same function by passing it the uid of the string, and since we compare it with pid in the if condition, the next run will definitely grab the child elements. The foreach loop iterates over all rows whose parent uid matches the passed value, so by restarting itself, the function will run on each element of each level. For clarity, we also pass level by increasing it by one. As a result, we will see which document has which level of nesting.

    Output array $array to browser:

    Not bad anymore, right?

    Now let's complicate our function a bit:

    $array = array(); //output array $array_idx_lvl = array(); //index by field level function recursive($data, $pid = 0, $level = 0, $path = "", $access_parent = "inherit")( global $array; global $array_idx_lvl; //Index by level global $groups; //domain groups //loot through the rows foreach ($data as $row) ( //We start with the rows whose pid is passed to the function, we have it 0, i.e. the root of the site if ($row["pid "] == $pid) ( //Collect the string into an associative array $_row["uid"] = $row["uid"]; $_row["pid"] = $row["pid"]; $_row[ "name"] = str_pad("", $level*3, ".").$row["name"]; $_row["level"] = $level; //Add a level $_row["path"] = $path."/".$row["name"]; //Add a name to the path $_row["view"] = ""; //Destroy access if($row["access"] == "inherit ") ( $_row["access"] = $access_parent; //If there is inheritance, do it like the parent ) else ( $_row["access"] = (in_array($row["access"], $groups)) ? "allow" : "deny"; ) $array[$row["uid"]] = $_row; //Resulting array indexed by uid //For quick fetching by level, form m index $array_idx_lvl[$level][$row["uid"]] = $row["uid"]; //The row has been processed, now run the same function for the current uid, i.e. //the child row (for which this uid is the pid) will be reversed recursive($data, $row["uid"], $level + 1, $_row["path"], $_row["access"]); ) ) ) recursive($table); //Launch
    Let's sort it out in order:

    1. Added field path- to form the path, add "/" and the string name to the value, then pass the resulting value to the function, where the history repeats and the output is the path from the root to the element.

    2. The resulting array is now formed not in order, starting from zero, but with binding to uid - $array[$row["uid"]] = $_row;. In this case, this does not affect the operation of the script in any way, but we will need the ability to access the string by index, and not by iterating in a loop, when we analyze the tree pass in the opposite direction.

    3. Added index $array_idx_lvl = array();. We will also need this index later, the meaning is that the resulting set is not added into one heap, but divided into arrays indexed by level.

    4. Field Access. When a function runs itself, along with the rest of the parameters, it passes its permissions setting $_row["access"] children, and then the following happens, the rights are checked - if inheritance is set (inherit), then the parent's rights are applied, if not - through in_array we check if the domain group specified in access is among the groups of the logged-in user. If there is - add to the line allow (allow), otherwise deny (prohibition).

    Final result:

    Well, we figured out the descent, now it remains to deal with the ascent and filling in the last field view A that determines the visibility of the elements. At the beginning of the article, I said why this is needed, but we can assume a different situation. Let's say you decide to link a tree-like list to the site's navigation menu, made in the form of a multi-level drop-down list with a bunch of items, and you just don't want a user who has access to just one document to move this entire array and look for his item in the bulk menu, because in fact, he needs to show only one branch leading to the desired button.

    Why is a return passage needed here? Suppose the user has access to all content except for one, the farthest (at the last level) document, if you think about it, it would be logical to start from the available one and lead it to the root of the tree, showing only the necessary elements.

    Let's get started:

    //Function to traverse up the tree one level from the given uid, sets //the visibility property to itself and the parent depending on the access or previously set visibility... function backRecursive($uid, $view = null, $ident = 0) ( global $array; //If you have gone up no more than one level if($ident<= 1) { //Если видимость уже есть - не меняем текущую строку, иначе //проверяем доступ и то что пришло от дочки if($array[$uid]["view"] != "show") { $array[$uid]["view"] = ($array[$uid]["access"] == "allow" or $view == "show") ? "show" : "hide"; } backRecursive($array[$uid]["pid"], $array[$uid]["view"], $ident+1); } }
    What this function does - takes as a parameter id the line to start acting on, accesses this line and checks the visibility. If the view field is not show (i.e. show), but something else, it checks what is safe, and if it is there allow(access granted), makes the element visible, otherwise hidden( hide), then launches itself, passing its pid and the visibility setting, as well as the variable $ident increased by 1, thereby blocking subsequent self-starts. During the second pass, according to the transmitted pid the parent element is found, the same check is performed, except for one, if from the child in a variable $view transferred" show", then no matter what, the current element will also be assigned show, which is visible.

    In my opinion, working with a limiter is the best option, because imagine the situation, at level 10 we have 100 documents, in order to completely traverse the entire tree, we need to run this function on each element, because if at the last level we run the function 100 times, then performing self-starts, iteration will reach the root 100 times. If you multiply by 10 levels, you will already get 1000 cycles, which is not good, so the rise must be carried out evenly, level by level.

    The following code triggers this function:

    Function startBack()( global $array_idx_lvl; $levels = array_keys($array_idx_lvl); //Get an array of levels $maxLevel = max($levels); //Find the deepest level of the tree //Loop through each level starting from the largest for ($i = $maxLevel; $i > 0; $i--) ( $uids = array_keys($array_idx_lvl[$i]); //At the current level, iterate over all elements and for each initiate processing and movement to lvl 1 foreach ($uids as $uid) ( backRecursive($uid); ) ) )
    This is where the index by level was required. Here we move from the farthest level, going into each one, processing each element in it.

    And here is the picture:

    Before launching, I deliberately set the permission group for the “Report for tax” item in order to clearly show that the code is working correctly. Despite the fact that access to the section "Accounting statements" is closed, it is visible.

    That's all, I think we coped with the task, the basis is obtained, the algorithm works, it can be applied in a real system.

    Today I will tell you how to MySQL create a hierarchical tree.

    Such trees are used when building categories of a dynamic site, for example, in an online store or when displaying comments on a post.

    In general, they are built wherever possible. The main thing is to build and apply it correctly.

    The most important thing when building a hierarchical tree is the correct database structure! For example, consider the structure of the database where the categories of the site are stored. For a simple example, the table will have 3 fields:

    1. id- category key
    2. parent_id— id of the parent category
    3. name- title of the section

    Let's create a table by executing a SQL query in PHPMyAdmin:

    CREATE TABLE `categories` (`id` INT NOT NULL AUTO_INCREMENT , `parent_id` INT NOT NULL , `name` VARCHAR(50) NOT NULL , PRIMARY KEY (`id`));

    Now we need to fill our table with records. As a result, you should get something like this table:

    You can populate the test table with the query:

    INSERT INTO `categories` (`id`, `parent_id`, `name`) VALUES (1, 0, "Section 1"), (2, 0, "Section 2"), (3, 0, "Section 3" ), (4, 1, "Section 1.1"), (5, 1, "Section 1.2"), (6, 4, "Section 1.1.1"), (7, 2, "Section 2.1"), (8 , 2, "Section 2.2"), (9, 3, "Section 3.1");

    And now attention! Further, logically, you need to make selections from the database in a loop to select each category and its subcategory. BUT! Well, if there are several categories in the database, which is also not correct in principle. And if the site is an online store and it has a hundred categories and the same number of subcategories? Then trouble! An unknown number of queries to the database will slow down the site or completely crash the mysql server.

    You can use only one query to the database to select all categories and their subcategories.

    Let's make a request and form a convenient array for further work.

    //Selecting data from the database $result=mysql_query("SELECT * FROM categories"); //If there are records in the database, we form an array if (mysql_num_rows($result) > 0)( $cats = array(); //In the loop, we form an array of sections, the key will be the id of the parent category, as well as an array of sections, the key will be category id while($cat = mysql_fetch_assoc($result))( $cats_ID[$cat["id"]] = $cat; $cats[$cat["parent_id"]][$cat["id"]] = $cat; ) )

    Selecting all data from a table categories and form an associative array $cats, the key will be the id of the parent category.

    Now we will build a tree. For construction we will use recursive function.

    The hierarchical tree will have the following structure:

    • Section 1
      • Section 1.1
        • Section 1.1.1
      • Section 1.2
    • Section 2
      • Section 1.1
      • Section 1.2
    • Section 3
      • Section 3.1

    Let's create a recursive function build_tree() . It will build our hierarchical tree of absolutely any nesting.

    Function build_tree($cats,$parent_id,$only_parent = false)( if(is_array($cats) and isset($cats[$parent_id]))( $tree = "

      "; if($only_parent==false)( foreach($cats[$parent_id] as $cat)( $tree .= ""; ) )elseif(is_numeric($only_parent))( $cat = $cats[$parent_id ][$only_parent]; $tree .= "
    • ".$cat["name"]." #".$cat["id"]; $tree .= build_tree($cats,$cat["id"]); $tree .= "
    • "; ) $tree .= "
    "; ) else return null; return $tree; )

    The function takes an array of sections and a section id. In the loop, we go through the subcategories and if they have more sections, then the function is run again with new parameters (a new array of sections and the id of the section to be built). This is how a tree of any nesting is formed!

    To build a tree, we write in the code:

    echo build_tree($cats,0);

    So, in two steps, we created a hierarchical tree of site sections and it doesn’t matter how many sections there are!

    UPD If you need a category tree in reverse order knowing the category id, then you need to use the function:

    Function find_parent ($tmp, $cur_id)( if($tmp[$cur_id]["parent_id"]!=0)( return find_parent($tmp,$tmp[$cur_id]["parent_id"]); ) return ( int)$tmp[$cur_id]["id"]; )

    This function takes an array of categories, the key of which is the id of the category, and the id of the category from which to go up.

    To build such a tree, run the build_tree function with the following parameters:

    echo build_tree($cats,0,find_parent($cats_ID,YOUR_CATEGORY_ID));

    Have questions? Ask in the comments

    Most programming languages ​​support recursive functions, that is, functions that call themselves. This is a very powerful tool that allows you to create quite elegant and functional programs, but it is used quite rarely - apparently because of its complexity for novice programmers and the ability to cause unpleasant errors. Therefore, in today's note, we will talk about creating and using recursive functions in PHP.

    Technically, recursive functions are no different from ordinary ones. The only difference is that somewhere in the code of the function there is a call to itself. For example, if you write

    Function test() (
    different operators
    test();
    different operators
    }
    then it will be a recursive function.

    The main difficulty when working with recursive functions is that they are quite easy to loop - in order for this not to happen, you need to be very careful about the possibility of exiting the function without a recursive call, otherwise the function will start "infinitely" calling itself and quickly exhaust computer resources.

    One of the most common uses of recursive functions is tree traversal, and today we'll try to break down a few examples. The first thing that comes to mind is the output of a multidimensional array. Of course, PHP has a print_r() function, but we will try to make the result more beautiful and suitable for viewing through a browser.

    Function print_array($ar) (
    static $count;

    $count = (isset($count)) ? ++$count: 0;

    $colors = array("#FFCB72", "#FFB072", "#FFE972", "#F1FF72",
    "#92FF69", "#6EF6DA", "#72D9FE");

    if ($count > count($colors)) (
    echo "Maximum dive depth reached!";
    $count--;
    return;
    }

    if (!is_array($ar)) (

    ";
    return; )

    echo "

    ";
    echo " \n";
    if (is_array($v)) (
    echo " \n";
    }
    }
    echo "
    $k$v
    ";
    print_array($v);
    echo "
    ";
    $count--;
    }

    This function uses the $count static variable, which will contain the "diving depth" of the function - we will use it to color nested arrays in different colors, depending on the nesting depth. Static variables retain their value when the function exits, and it is quite convenient to use them during recursion. Of course, you could just as well pass the $count variable as a parameter, but static variables are more convenient to use...

    The $colors array contains a list of different colors that will be used for coloring. At the same time, we use it to limit the maximum level of recursion - as soon as all the colors are exhausted (checking if ($count >= count($colors))), the function will display a message that the maximum depth has been reached.

    Just in case (by the way, it is always recommended to do this to avoid any unpleasant consequences), we also check that the array was passed as an argument - if this is not the case, then we simply display an error message and exit the function:

    If (!is_array($ar)) (
    echo "Passed argument is not an array!

    ";
    return;
    }

    We then "open" the table (echo "

    ";) and start sequentially viewing the array passed as an argument.

    While(list($k, $v) = each($ar)) (
    ...
    }

    It is one of the standard ways to step through an array - in each pass of the loop, the variables $k and $v are assigned the following index (or, as it is also called, key) values ​​and array element values.

    Having received a key-value pair, we display it in a table row:

    echo"

    \n";
    Note that if the value of this element is an array, then the word "array" will be printed. Now we check if the value is an array:
    if (is_array($v))
    and if yes, then we print (not completely!) one more line of the table, skipping the index (it is already on the previous line):
    echo " \n";
    This completes the processing of the current key-value pair, and the while loop moves on to the next pair. And when the entire array has been passed, then we just have to close the table:
    echo "
    $k$v
    ";
    and call our array print function, passing the nested array as an argument:
    print_array($v);
    Then (after the recursively called function has finished its work) we "close" the row of the table (note that since our function prints the "full table" - from before
    , - we do not need to close the nested table - the function will take care of this itself - but we only need to close the cell and row of the current table).
    echo "
    ";
    and decrease the depth value
    $count--; From what was written above, it is clear that our function does not know anything about whether it is called recursively or not, and it does not matter to it - the main thing is that an array is passed as an argument. Similarly, when calling itself to process a nested array, it doesn't care that it's a recursive call - it's no different than calling some other function. And the programmer's concern is to ensure that recursive calls end somewhere, that is, the function can finish its work without calling itself again. As soon as this happens, the depth of nested calls will begin to decrease, and eventually the function will "surface".

    The result of such a function will look something like this (in the example, the maximum depth is limited to the third level, for clarity, the output of the $count variable is added, and the array for printing is set to array(1, array(1.1, 1.2, 1.3), 2, 3 , array(3.1, 3.2, array(3.21, 3.22, 3.23, array(3.231, 3.232), 3.24)), 4)

    countkeyvalue
    0 0 1
    0 1 array
    0
    countkeyvalue
    1 0 1.1
    1 1 1.2
    1 2 1.3
    0 2 2
    0 3 3
    0 4 array
    0
    0 5 4 Printing an array is rarely needed "in real life", except for testing your scripts, but recursion can come in handy quite often. For example, a more vital need is to process all files in a directory, including subdirectories. Let's say it's convenient to use the recursive dd() function (short for Directory Delete) to delete files: function dd($file) (
    if (file_exists($file)) (
    chmod($file,0777);
    if (is_dir($file)) (
    $handle = opendir($file);
    while($filename = readdir($handle))
    if ($filename != "." && $filename != "..") dd($file."/".$filename);
    closedir($handle);
    rmdir($file);
    ) else (
    unlink($file);
    }
    }
    }

    It works on exactly the same principle - if a file is passed as an argument, then it is deleted (unlink($file);), and if a directory, it is opened, sequentially viewed, and the same function is called for each file (including subdirectories) dd()...

    Another example where it is very convenient to use recursive functions is reading files over the HTTP protocol with redirect tracking - you need to open a connection, request a file (or just a header) and check the server response - if it contains a header

    Then you need to close the connection and call the file reading function again, but with a new address. Writing such a function can be offered as a "homework", but keep in mind that it must be approached carefully - you should definitely save a list of all redirects (it is convenient to use a static array for this) and compare addresses, otherwise the function can very easily get stuck if the redirects go "round".

    That, perhaps, is all. I hope that the use of recursive functions will be able to replenish your "programmer's arsenal" with another powerful tool...

    Views