Put simply, recursion occurs in computer science when defining a function that calls itself somewhere in its own function definition. A function with this characteristic is aptly referred to as a recursive function. The goal of doing this is breaking a seemingly large, possibly even infinite task into its most basic steps. A good way of understanding how recursion approaches a problem is to contrast it with a style of programming we became familiar with in CSC108, iteration. A simple example of an iterative function would be a function that continues appending to a list while a condition is not met. Both iteration and recursion try to break problems down into smaller tasks, but as far as my understanding goes, iterative functions tend to explicitly declare when it has reached a specific end while recursive functions seem to be more focused on breaking down the problem into its most simple form--referred to as the base case. The base case is the true core of a recursive function in that calling a recursive function on the base case will always lead directly to the return statement. If a more complicated case is called into the recursive function, the goal is to break it down into the base case in as many steps as is required. This 'breaking down' occurs by checking if the base case if fulfilled, if it isn't, the input is modified in some way and the function is called again until either the base case has been reached or the input has been simplified so it is one step closer to it.
As an example of a recursive function, here is one from one of the labs on recursion that my lab partner Brian (check out his entry on recursion right here) and I wrote:
def find_num(SL: 'SearchList', n: int) -> bool:
"""
Return True if n is in SL, otherwise False.
"""
if n == SL[0]:
return n == SL[0]
elif n < SL[0] and SL[1]:
return find_num(SL[1], n)
elif n > SL[0] and SL[2]:
return find_num(SL[2], n)
else:
return False
The point of this function is to recursively search a SearchList, an ADT we learned about in the lab. A SearchList has some interesting characteristics. First, a SearchList is either None or a python list of three elements. Element 0 is always an integer, element 1 is either a None or a SearchList containing integers smaller than element 0, and element 2 is either None or a SearchList containing integers larger than element 0. For example, a SearchList can look like this: [5, [2, [1, None, None], [3, None, None]], [6, None, None]].
Using these specific characteristics, the previous function was written to recursively scan a SearchList for a given integer n. The first if statement checks for the base case, which is the situation where the first element is the element we are looking for. This is a requirement for any recursive function, and is a good way of displaying how a function defines the simplest possible task. For example, calling find_num(SL, 9) on a SearchList defined as [9, None, None] would evaluate to True without calling itself again. An example of a more complex case is the SearchList [8, [2, None, None], None]. It is immediately apparent that this is not the base case since there is a nested SearchList as element two. When find_num is called on this SearchList for n = 2, the base case is checked and will evaluate to false. Next, elif n < SL[0] and SL[1] will be evaluated. n is less than SL[0], which is 8, and SL[1] will evaluate to True since it is not None, meaning there exists a nested SearchList for the function to check for the intended value. This is where recursion happens, inside this elif statement the function calls itself, this time with the nested SearchList in the parameter (this is indexed so it is always re-evaluated). In this second level, the function will once again check for the base case, and this time it will return True. This return will be carried up to the 'first level' of the function and will end the function call.
This might not be the simplest possible example but I believe it effectively shows every key feature of a recursive function: the check for the base case, the return statement(s) for when the function has or has not reached a base case, the function calling itself with a modified parameter, etc. Regardless of how many nested SearchLists there are, this function will continue to call itself until a base case has been found, no matter how 'deep' it must go. A lot of power in a relatively simple function like this seems to be recursions biggest strength and whenever a problem can be broken down into the same series of steps it is definitely a viable option.
As an example of a recursive function, here is one from one of the labs on recursion that my lab partner Brian (check out his entry on recursion right here) and I wrote:
def find_num(SL: 'SearchList', n: int) -> bool:
"""
Return True if n is in SL, otherwise False.
"""
if n == SL[0]:
return n == SL[0]
elif n < SL[0] and SL[1]:
return find_num(SL[1], n)
elif n > SL[0] and SL[2]:
return find_num(SL[2], n)
else:
return False
The point of this function is to recursively search a SearchList, an ADT we learned about in the lab. A SearchList has some interesting characteristics. First, a SearchList is either None or a python list of three elements. Element 0 is always an integer, element 1 is either a None or a SearchList containing integers smaller than element 0, and element 2 is either None or a SearchList containing integers larger than element 0. For example, a SearchList can look like this: [5, [2, [1, None, None], [3, None, None]], [6, None, None]].
Using these specific characteristics, the previous function was written to recursively scan a SearchList for a given integer n. The first if statement checks for the base case, which is the situation where the first element is the element we are looking for. This is a requirement for any recursive function, and is a good way of displaying how a function defines the simplest possible task. For example, calling find_num(SL, 9) on a SearchList defined as [9, None, None] would evaluate to True without calling itself again. An example of a more complex case is the SearchList [8, [2, None, None], None]. It is immediately apparent that this is not the base case since there is a nested SearchList as element two. When find_num is called on this SearchList for n = 2, the base case is checked and will evaluate to false. Next, elif n < SL[0] and SL[1] will be evaluated. n is less than SL[0], which is 8, and SL[1] will evaluate to True since it is not None, meaning there exists a nested SearchList for the function to check for the intended value. This is where recursion happens, inside this elif statement the function calls itself, this time with the nested SearchList in the parameter (this is indexed so it is always re-evaluated). In this second level, the function will once again check for the base case, and this time it will return True. This return will be carried up to the 'first level' of the function and will end the function call.
This might not be the simplest possible example but I believe it effectively shows every key feature of a recursive function: the check for the base case, the return statement(s) for when the function has or has not reached a base case, the function calling itself with a modified parameter, etc. Regardless of how many nested SearchLists there are, this function will continue to call itself until a base case has been found, no matter how 'deep' it must go. A lot of power in a relatively simple function like this seems to be recursions biggest strength and whenever a problem can be broken down into the same series of steps it is definitely a viable option.