Launch Radio     |     Flip Skins

Writing Recursive Functions
As intimidating as it might sound, recursive functions are actually quite simple. A recursive function is a function that calls itself. This simple but elegant concept can do A LOT of heavy lifting in situations where you're dealing with open ended, undefined, or theoretically (but not actually) infinite data or directory structures. For example, there may be no limit to the number of levels of Manager to Employee hierarchies in the data structure of an Employee database. We know that the number of levels is finite, but perhaps at the time when we're writing our code when don't know how many levels there are or we may know that the number of levels can change. Similarly, there is no theoretical limit to parent-child folder relationships in a directory hierarchy. A folder can contain a folder that can contain a folder that can contain a folder .... on and on.

Let's look at 2 examples where recursive functions can do major things for us in these open-ended scenarios. The first example will deal with a data structure and the 2nd example will deal with a directory structure.

WARNING!  The dangerous downside of recursive functions is that, if not written correctly, they can lead to infinite loops that run forever and bring your server crashing down.

The Manager Who Moved

No, this is not a Sherlock Holmes novel. Let's say there is a business rule/policy that when a manager moves to a new state and this information is updated in the system, the same should happen for his employees, and their employees, and their employees ... This may sound daunting at first, but here's a simple recursive function that will take care of this for us.

<cffunction name="UpdateManagerState" output="false">
     <cfargument name="ManagerID" required="yes" type="numeric">
     <cfargument name="NewState" required="yes" type="string">

     <cfquery name="ManagerUpdateQuery" datasource="MAXO">

          UPDATE tblEmployees
          SET State = '#NewState#'
          WHERE EmployeeID = #ManagerID#
     </cfquery>

     <cfquery name="EmployeeList" datasource="MAXO">

          SELECT EmployeeID
          FROM tblEmployees
          WHERE ManagerID = #ManagerID#
     </cfquery>

     <cfloop query="EmployeeList">
          <cfset UpdateManagerState(EmployeeID,NewState)>
     </cfloop>
</cffunction>

Let's take a closer look at what we got here. First, notice that we create a function that does NOT return anything. It performs certain tasks for us but there's no return variable. Recursive functions CAN have return variables (for example the number of records updated or possible error messages) but this one doesn't. Also, notice that our function has 2 required input parameters (arguments). As you would expect, these 2 are the manager's unique Employee ID and the new state that he's moving to.

The first thing we do is simply update the manager's record. This is a fairly basic update query,ManagerUpdateQuery, which updates the managers state using his Employee ID.

Next, we grab a list of all of HIS Employees (just their ID's) using the 2nd query in the function, Employee List.

Now we iterate (loop) through the list of his Employees and here's where the recursive part of the function comes in. We call the same function that we're in for each of his employees and pass the Employee ID and new state to it.<cfset UpdateManagerState(EmployeeID,NewState)> So now that employee's record will get updated and if he has any employees, their info, and their employees' info will get updated ... and on and on.

That's it! Clean, concise, readable, easy to edit, and EXTREMELY powerful.

Updating File Names

Let's say you just moved a very deep directory structure to a new unix environment. You've got hundreds of nested folders and thousands or millions of files in these directories. But there's a big problem. The space characters in the file names that were harmless in the windows environment have now rendered your files unreadable and reeked havoc on your code. You need to update the file names quickly. You decide the best protocol would be to replace the space characters in each file name with underscores. So "March Income Statement.xls" would be come "March_Income_Statement.xls". Great. But who has time to dig through ALL those directories and manually update the file names? Don't worry. I done told you befo' ... MAXO's got your back!

We'll write a recursive function that updates directory names and the name of everything inside that directory based on the new protocol discussed above:

<cffunction name="RepairFileList" output="false">
     <cfargument name="StartingFolder" required="no" default="C:\inetpub\wwwroot" type="string">

     <cfset FileDirectory = ExpandPath("#StartingFolder#")>
     <cfdirectory action="list" name="FileList" directory="#FileDirectory#">

     <cfloop query="FileList">
          <cfset OldName = Name>
          <cfset NewName = Replace(OldName," ","_","ALL")>
          <cfset OldPath = FileDirectory & "/" & OldName>
          <cfset NewPath = FileDirectory & "/" & NewName>
          <cfif Type Is "File">
               <cffile action="rename" source="#OldPath#" destination="#NewPath#">
          <cfelseif Type Is "Dir">
               <cfdirectory action="rename" directory="#OldPath#" newdirectory="#NewPath#">
               <cfset RepairFileList("#NewPath#")>
          </cfif>
     </cfloop>
</cffunction>

We've created a new function RepairFileList. This function has one optional input parameter and that is the starting directory,StartingFolder . If it doesn't get one, it starts at the root folder.

The first thing the function does is get a list of all Files and Folders inside the starting directory, <cfdirectory action="List" ... Then it iterates (loops) through the list and determines the new file name and path based on the old file name and path. If the object is a file, then the name is cleaned up and updated. If the object is a directory, the name is cleaned up and updated AND the function calls itself for that directory as well, using the directory's new name & path <cfset RepairFileList("#NewPath#")>.

Well, I hope this has helped you learn more about recursive functions. Now, at the next meeting, when someone brings up some global problem that will require 100's of hours of manual work you can lean back in your seat, put your arms behind your head, and casually mention that you can quickly fix the issue using recursive functions. OR... agree with the estimate, get the job done in one day, charge them for 30 and hit Cancun. The choice is yours my friend.


© 2005 - 2012, MAXO Studio