So what is recursion? it basically is a process of repeatedly breaking the bigger problem- which is either hard or seem like impossible to solve - to smaller problem which is easier to solve. It's more or less like the poof technique that i learnt in CSC165 called 'induction'. But in recursion, rather than proving the basis step, i have to find the solution to base case (often known as basis step of a recursive function). And according to blog.ezyang.com, i can use induction to prove whether my recursive function is correct since inductive hypothesis in induction behaves like recursive call in recursion! Which sounds very interesting but the prof didn't mention about it in CSC165, may be it was too advanced for the first year.
A question came to my mind when i first made a recursive function 7 weeks ago is that, "why do i have to make a recursive function while i can use 'for' or even 'while' loop to solve the problem". And after working through all of the exercises, term test and especially the assignment 1, i got pretty good ideas why do we need recursive function.
Let consider an example:
def slog_example1(list1): # without using recursion
"""(list of int) --> int
return sum of list1 which has max list depth of 3.
>>> slog_example1([1,[2, 3, [4, 5], 1]])
16
"""
total = 0
for i in list1: # 1 time
if isinstance(i, int):
total += i
elif isinstance(i, list):
for i in i: # repeat 2nd time
if isinstance(i, int):
total += i
elif isinstance(i, list):
for i in i: # repeat 3rd time
total += i
return total
def slog_example2(list2):Two functions slog_example1 and slog_example2 are both performing the same task which is calculating the sum of integer elements inside the nested list. The recursive function slog_example2 can calculate the sum of integer within the list of any depth(max: 979 - check out my first extra post), whereas the maximum list depth that can be afforded by the iterative function slog_example1 is only 3 since i only repeated my 'for' loop 3 times. So unless i am patient enough to retype the 4 lines within my 'for' loop 980 times- which is rather a crazy thing to do, i would refer to use the recursive function slog_example2 to solve the problem! Imagining that if the same problem is given in the exam with infinite list depth which method should you choose?
"""(list of int) --> int
return sum of list2 which has unlimited list depth.
>>> slog_example2([1,[2, 3, [4, 5], 1]])
16
>>> slog_example2([[[[[[[[[[[[1]]]]]]]]]]],1])
2
"""
if isinstance(list2, list):
return sum([slog_example2(n) for n in list2])
else:
return list2
So, the first obvious advantage of recursion over iteration is that it turns a problem that seems impossible into a possible problem. But now let consider another version of slog_example2:
def slog_example2v2(list2):That is the beauty of list comprehension that allows me to turn 980x4 lines of iterative code into 1 single line of recursive code. Recursion together with list comprehension not just make the code look elegant but save programmer's time and possibly money. Moreover, in some case, the recursive solution is even more understandable than the iterative solution since a recursive solution is usually very short, typically from 1 to 10 lines.
"""(list of int) --> int
return sum of list2 which has unlimited list depth.
>>> slog_example2v2([[[[[[[[[[[[1]]]]]]]]]]],1])
2
"""
return sum([ n if isinstance(n, int) else slog_example2v2(n) for n in list2])
We now look at the time complexity of those three function. If you copy and run the following code in your python, you would be able to figure out which one of the iterative, recursive or list comprehension+recursive take longest and shortest time to process (check out my second extra post):
import time
def time_test():
start = time.time()
slog_example1([1,[2, 3, [4, 5], 1]])
a = time.time()-start
print('iteration takes: ' + str(time.time()-start) + ' to run')
start = time.time()
slog_example2([1,[2, 3, [4, 5], 1]])
b = time.time()-start
print('recursion takes: ' + str(time.time()-start) + ' to run')
start = time.time()
slog_example2v2([1,[2, 3, [4, 5], 1]])
c = time.time()-start
print('list comprehension + recursion takes : ' + str(time.time()-start) + ' to run')
dict_time = {'iteration': a, 'recursion': b, 'lc+rec': c}
print('longest time: ', max(dict_time, key = dict_time.get)) # return the method that takes longest time
print('shortest time: ', min(dict_time, key = dict_time.get)) # return the method that takes shortest time
The result is that for a nested list of any depth and at any length, the recursive code (without list comprehension) always take longest time to process, whereas, the iterative code though is longer take least amount of time. So i can conclude, although a recursive solution is generally very short, it is not necessary that it takes less time than an equivalently lengthly iterative solution. Time complexity therefore is the biggest disadvantage of using recursion (at least in the programming language like Python).
My last point is about debugging, in my own experience, it is much harder to debug a recursive function since it requires me to keep track of the whole stack consisting the results to many smaller problems not just the variables like in iterative function.