1

The following example is from "Think Python" book by Allen Downey. While explaining the concept of "Memos" in dictionaries, he quotes the following example.

known = {0:0, 1:1}
def fibonacci(n):
    if n in known:
        return known[n]
    res = fibonacci(n-1) + fibonacci(n-2)
    known[n] = res
    return res
fibonacci(5)
print known

I was expecting this code to return {0:0, 1:1, 5:5} since I don't see any iteration to compute the function for each value between 1 and 5. But what I see is {0: 0, 1: 1, 2: 1, 3: 2, 4: 3, 5: 5} returned when the code is run (as the book says it would), but I can't understand why the function computed expression res = fibonacci(n-1) + fibonacci(n-2) for n=2, n=3 and n=4.

Can someone please explain this to me? I can't quite get the explanation in the book.

jonrsharpe
  • 115,751
  • 26
  • 228
  • 437
Kumar Dasari
  • 11
  • 1
  • 2
  • 4
    This is *recursion*, not *iteration*. Why do you think SO can explain it any better than the book can? – jonrsharpe Jan 16 '16 at 22:33
  • 2
    Code analysis is not pattern matching, in order to understand what is happening you should follow each line of code, take a piece of paper and simulate what happens - this is the best way to learn, and the answer to this question will appear quite easily – lejlot Jan 16 '16 at 22:35
  • Step away from the computer and follow the algorithm yourself. You'll do exactly the same thing the computer does. – Sam McCreery Jan 16 '16 at 22:36
  • 1
    A pair of recursive calls is the naive recursive Fibonacci implementation, by the way. – TigerhawkT3 Jan 16 '16 at 22:40
  • jonrsharpe: I thought posting here will help me, because I can't expect book author to take time to interact with all the readers, where as with SO, I can interact with the whole community and can possibly get answer to my question, as I was able to. Thank you. – Kumar Dasari Jan 16 '16 at 23:19
  • I totally, 100% get it, now. As I said I am a rookie here, and now I see what my mistake was. I didn't see recursion at first, because in my mind (I don't know how and why), I was fixated on "res = (n-1) + (n-2)" where as the function clearly states "res = fibonacci(n-1) + fibonacci(n-2)" thus calling the function from within itself. Everyone who responded saw it right away. I hope I will be able to develop cognitive recognition of items in code as I deal more and more with the code. It is crystal clear now. Thanks to every one who responded. – Kumar Dasari Jan 18 '16 at 01:57

2 Answers2

4

Try putting print statements into the code to keep track of the state of known:

def fibonacci(n):
    print(n, known)
    if n in known:
        return known[n]
    res = fibonacci(n-1) + fibonacci(n-2)

    known[n] = res
    return res
fibonacci(5)
print(known)

yields

5 {0: 0, 1: 1}
4 {0: 0, 1: 1}
3 {0: 0, 1: 1}
2 {0: 0, 1: 1}
1 {0: 0, 1: 1}
0 {0: 0, 1: 1}
1 {0: 0, 1: 1, 2: 1}
2 {0: 0, 1: 1, 2: 1, 3: 2}
3 {0: 0, 1: 1, 2: 1, 3: 2, 4: 3}
{0: 0, 1: 1, 2: 1, 3: 2, 4: 3, 5: 5}

The first (integer) value is the value of n. As you can see, fibonacci(5) is called, then fibonacci(4), then fibonacci(3), then fibonacci(2) and so on. These calls are all due to Python encountering

    res = fibonacci(n-1) + fibonacci(n-2)

and recursively calling fibonacci(n-1). Remember, Python evaluates expressions from left to right. So only after fibonacci(n-1) returns is fibonacci(n-2) called.

To better understand the order of the recursive function calls you could use this decorator:

import functools

def trace(f):
    """This decorator shows how the function was called.
    Especially useful with recursive functions."""
    indent = ' ' * 2

    @functools.wraps(f)
    def wrapper(*arg, **kw):
        arg_str = ', '.join(
            ['{0!r}'.format(a) for a in arg]
            + ['{0} = {1!r}'.format(key, val) for key, val in kw.items()])
        function_call = '{n}({a})'.format(n=f.__name__, a=arg_str)
        print("{i}--> {c}".format(
            i=indent * (trace.level), c=function_call))
        trace.level += 1
        try:
            result = f(*arg, **kw)
            print("{i}<-- {c} returns {r}".format(
                i=indent * (trace.level - 1), c=function_call, r=result))
        finally:
            trace.level -= 1
        return result
    trace.level = 0
    return wrapper

known = {0:0, 1:1}
@trace
def fibonacci(n):
    # print(n, known)
    if n in known:
        return known[n]
    res = fibonacci(n-1) + fibonacci(n-2)

    known[n] = res
    return res
fibonacci(5)
print(known)

which yields

--> fibonacci(5)                         
  --> fibonacci(4)                        # fibonacci(5) calls fibonacci(4)
    --> fibonacci(3)                      # fibonacci(4) calls fibonacci(3)
      --> fibonacci(2)                    # fibonacci(3) calls fibonacci(2)
        --> fibonacci(1)                  # fibonacci(2) calls fibonacci(1)
        <-- fibonacci(1) returns 1
        --> fibonacci(0)                  # fibonacci(2) calls fibonacci(0)
        <-- fibonacci(0) returns 0
      <-- fibonacci(2) returns 1
      --> fibonacci(1)                    # fibonacci(3) calls fibonacci(1)
      <-- fibonacci(1) returns 1
    <-- fibonacci(3) returns 2
    --> fibonacci(2)                      # fibonacci(4) calls fibonacci(2) 
    <-- fibonacci(2) returns 1
  <-- fibonacci(4) returns 3
  --> fibonacci(3)                        # fibonacci(5) calls fibonacci(3) 
  <-- fibonacci(3) returns 2
<-- fibonacci(5) returns 5
{0: 0, 1: 1, 2: 1, 3: 2, 4: 3, 5: 5}

You can tell by the level of indentation where each recursive call is coming from.

unutbu
  • 842,883
  • 184
  • 1,785
  • 1,677
  • Thanks for the explanation. It is very helpful and on point in clearing up my confusion. I am a beginner for coding and have not come across the the recursion in the context of function evaluation. I am familiar with "recursion" in the domain of my expertise (IP routing), where it is a much simpler concept. Thanks for taking time to explain this. – Kumar Dasari Jan 16 '16 at 23:12
0

As you see fibonacci is a recursive function, which means it calls itself inside the function.

For example consider fibonacci(2). 2 is not in knowndictionary, so res = fibonacci(1)+fibonacci(0) is executed. Because 0 and 1 are known , their values (0 and 1) adding into res so res=1 so fibonacci(2) = 1 and 2 is also added into the known dictionary and it goes until reaches 5.

jonrsharpe
  • 115,751
  • 26
  • 228
  • 437
ᴀʀᴍᴀɴ
  • 4,443
  • 8
  • 37
  • 57