[Tutor] recursive function example
spir
denis.spir at gmail.com
Wed Dec 11 14:10:26 CET 2013
On 12/10/2013 03:48 PM, ugajin at talktalk.net wrote:
> [...]
Recursivity is hard to get really, meaning intuitively with your guts so-to-say.
Maybe using another example may help. Lets us say you want a function that sums
numbers from 1 up to n, the only input variable. (The result should thus be
n(n+1)/2.)
def sum_up_to (n):
# 'res' for result because 'sum' is a Python builtin symbol
res = 0
for i in range(1, n+1):
res += i
return res
print(sum_up_to(9))
Now, how to make this recursive? There are multiple ways, all based on:
# intuitive def:
sum_up_to(n) = 1 + 2 + ... + n
# recurrence def:
sum_up_to(n) = sum_up_to(n-1) + n
sum_up_to(1) = 1
We want to just reproduce this second def stupidly in code:
def sum_up_to_rec1 (n):
if n == 1:
return 1
else:
return n + sum_up_to_rec1(n-1)
print(sum_up_to_rec1(9))
This does not help much and understand the recursion procedure yet, si let use
add some debug output rightly placed:
def sum_up_to_rec1 (n):
print("start of func ; n : %d" % n, end=" | ")
if n == 1:
return 1
else:
sub_res = sum_up_to_rec1(n-1)
print("n : %d ; sub_res : %d" % (n, sub_res))
return n + sub_res
print(sum_up_to_rec1(9))
Run it.
The surprising point, maybe, is that all "start of func..." outputs get written
in a row with all "sub_res..." outputs coming on lines one under the other. This
is the core of the recurrent logic: starting from 9, we call the func for 8;
nothing is yet computed, no result (thus nothing to print as sub_res). From 8,
we call the func for 7; nothing is yet computed, still. Etc... until 1, our stop
value, which finally returns a result. This result for 1 permits the call for 2
to (write the sub_res for 1 *at the end of the big row* and) complete, which
permits the call for 3 to (write the sub_res for 2 and) complete... etc until 9.
The call for 9 is out initial run, when it returns with its product, this is the
final result.
Thus, the logic is such: the execution cascade falls here top-down; but the
computation cascade inverts gravity and climbs down-up.
Does this help?
Now, why do we execute top-down? After all, the definition by recurrence allows
us very well to start from 1 and go up to n, it would work as well, so why not?
The reason is that we would need to keep n aside, since in this case it becomes
our stop value; but we still need some index, here going up from 1 to n. Thus,
we need another parameter to the recurrent func, say like in the easy func
above. And in fact we need yet a third parameter, namely the partial result: how
else would the next call know the partial result? This gives something like:
def sum_up_to_rec2 (n, i=1, sub_res=0):
# 'res' for result because 'sum' is a Python builtin symbol
print("start of func ; i : %d ; sub_res : %d" % (i, sub_res), end=" | ")
if i == n:
return i + sub_res
else:
sub_res += i
print("sub_call ; sub_res : %d" % sub_res)
return sum_up_to_rec2(n, i+1, sub_res)
print(sum_up_to_rec2(9))
Needless to say, this is more complicated (but strangely far easier to
understand for me). Another solution is to _define_ a new function, recursive,
inside the outer one which thus keeps a simple interface (no 'i' or 'sub_res').
This is very often needed in recursion in functional programming, because we
need to pass partial data anyway (state of partial execution). In fact, your
case and mine are not typical.
Thus, if we must define a new function anyway, we have the choice of running
upwards or downwards; downwards is the normal case for a weird reason, namely
that the typical data structure there is a linked list, to which one can only
(efficiently) put new items at start; thus, if we run forwards, the results are
backwards.
Advice: use recursion whenever it corresponds to the application; meaning the
idea you are expressing itself is recursive. This is the case in problems which
decompose into sub-problems *of the same form*. Otherwise, avoid forcing
recursion whenever it drives to a distortion of ideas.
There are situations where, like in our 2 cases, a simple conception is a linear
recursion (recursion just mean repetition, literally re-running), while an
alternative view is of self-similar recursion, like fractals (what we call
recursion in programming, see self-similarity in wikimedia). I used to program
both views to get used to recursive thinking.
Denis
More information about the Tutor
mailing list