A recursive function is a function that references itself. It allows a single function to use itself to continue processing. Let’s take a simple example of a struct with a nested struct as one of its values. The goal is to create a very simple function that will grab all values from the parent struct and convert them into an array. Our struct looks like the following.
And in case you need it the code to create the above struct.
Code to create Struct
<cfset variables.myStruct = structNew() /> <cfset variables.myStruct.firstName = "John" /> <cfset variables.myStruct.lastName = "Smith" /> <cfset variables.myStruct.address = structNew() /> <cfset variables.myStruct.address.street = "55 Washington St." /> <cfset variables.myStruct.address.city = "Providence" /> <cfset variables.myStruct.address.state = "RI" /> <cfset variables.myStruct.address.zip = "00001" />
So the goal is to loop through the struct and get all values and append them to an array. Without the address struct this would be simple. We could simply use the loop ‘collection’ to get each key, and then append the value of each key to a preset array. However, it’s not that simple because in the case of the address we don’t want the struct to be appended to the array, we want to dig into that struct and grab each of the values within it and append those to the array. This is where recursion comes in.
So think about it. If we have a simple function whose sole purpose is to take a struct, loop through it, and append any values into an array – then we simply need to check if the value being looped over is a struct, and if so have our function call itself passing it the nested struct.
Let’s take a look at the function itself.
<cfcomponent displayname="Process Struct" hint="Processes a struct with embedded structs recursively" output="false"> <!--- the array that will contain all struct values has to be in the member variables scope so that it lives independent of the recursive function ---> <cfset variables.myArray =  /> <cffunction name="convertValuesToArray" displayname="Convert Values to List" hint="Converts all values in struct into a list." output="false" access="public" returntype="Array"> <cfargument name="thisStruct" displayname="This Struct" hint="The struct to extract the values from." type="struct" required="true" /> <cfset var loc_item = "" /> <!--- loop over the struct keys ---> <cfloop collection="#arguments.thisStruct#" item="loc_item"> <!--- check if the value is another struct. If so re-run this function (recursion) ---> <cfif isStruct(arguments.thisStruct[loc_item])> <cfset convertValuesToArray(arguments.thisStruct[loc_item]) /> <!--- if not append the value into the array ---> <cfelse> <cfset arrayAppend(variables.myArray, arguments.thisStruct[loc_item]) /> </cfif> </cfloop> <!--- return the array ---> <cfreturn variables.myArray /> </cffunction> </cfcomponent>
So I have this function stored inside a component called ‘processStruct’. It takes a single argument, which is the parent struct to get values from. The recursion occurs within the loop. For each value in the struct it checks if that value is a struct using the built-in ‘isStruct()’ function. If it’s not a struct the value is appended into the array. If it is a struct we use recursion by simply having the function call itself and pass to it the nested struct.
Note that the array ‘variables.myArray’ is set outside of the recursive function so that its value is maintained each time the ‘convertValuesToArray()’ function calls itself. If it were locally scoped to the function each time a nested struct is found the array would be recreated in memory.
My full code that calls this function is as follows:
<cfset variables.myObj = createObject("component", "processStruct") /> <cfset variables.myStruct = structNew() /> <cfset variables.myStruct.firstName = "John" /> <cfset variables.myStruct.lastName = "Smith" /> <cfset variables.myStruct.address = structNew() /> <cfset variables.myStruct.address.street = "55 Washington St." /> <cfset variables.myStruct.address.city = "Providence" /> <cfset variables.myStruct.address.state = "RI" /> <cfset variables.myStruct.address.zip = "00001" /> <cfset variables.myAry = variables.myObj.convertValuesToArray(variables.myStruct) /> <cfdump var="#variables.myAry#">
Running this code leaves us with the following array of values.
Essentially recursion is used when you have a function that encounters a scenario that it can solve best. Just keep in mind that when a function calls itself any data stored locally within the function will be overwritten. As such any values in the function that need to persist throughout the recursion need to be stored outside of the local scope. I chose the variables scope, which keeps the data local to the instance of the component.