Search code examples
pythonrecursionpascals-triangle

where is this mystery number coming from in my pascal's triangle program?


I wrote a number to output the nth row of Pascal's Triangle:

import sys
def nth_row_pascal(n):

    def nth_num(i, j):

        if i==0:
            print("({},{}) determined to be 1".format(i,j))
            return 1
        if (j == 0) or (j==i):
            print("({},{}) determined to be 1".format(i,j))            
            return 1
        print("calculating pascal number: ({},{})".format(i,j))                
        result=nth_num(i-1, j-1) + nth_num(i-1, j)
        print("answer: " + str(result))
        return result

    result = [nth_num(i, int(n)-1) for i in range(int(n))]
    return result

print(nth_row_pascal(sys.argv[1]))

When I run

python pascal.py 4 

I get:

python3 pascal.py 4
(0,3) determined to be 1
calculating pascal number: (1,3)
(0,2) determined to be 1
(0,3) determined to be 1
answer: 2
calculating pascal number: (2,3)
calculating pascal number: (1,2)
(0,1) determined to be 1
(0,2) determined to be 1
answer: 2
calculating pascal number: (1,3)
(0,2) determined to be 1
(0,3) determined to be 1
answer: 2
answer: 4
(3,3) determined to be 1
[1, 2, 4, 1]

Why is

answer: 4 popping up?

It seems that the function nth_num will print out a statement -- either

  1. i,j determined to be 1 or
  2. calculating pascal number ...

But in this case it goes answer: 2 (understandable) answer: 4 (what??)

each time it is executed.


Solution

  • A lot of stuff happens in between the "calculating" and "answer" lines, including other recursive calls! Here's some ASCII art and some extra whitespace to help clear up where exactly that answer: 4 comes from:

    (0,3) determined to be 1
    
    calculating pascal number: (1,3)  >-----\
    (0,2) determined to be 1                |
    (0,3) determined to be 1                |
    answer: 2   <---------------------------/
    
    calculating pascal number: (2,3)  >-----\
                                            |
    calculating pascal number: (1,2)  >-\   |
    (0,1) determined to be 1            |   |
    (0,2) determined to be 1            |   |
    answer: 2   <-----------------------/   |
                                            |
    calculating pascal number: (1,3)  >-\   |
    (0,2) determined to be 1            |   |
    (0,3) determined to be 1            |   |
    answer: 2   <-----------------------/   |
                                            |
    answer: 4   <---------------------------/
    
    (3,3) determined to be 1
    

    When tracing recursive functions to understand how they work it can help to print out the depth of the recursive call, like this:

    from typing import List
    
    
    def nth_row_pascal(arg: str) -> List[int]:
        n = int(arg)
    
        def nth_num(i: int, j: int, depth: int) -> int:
            indent = "  " * depth  # for pretty-printing
            if i == 0:
                print(f"{indent}{(i, j)} determined to be 1")
                return 1
            if j in {0, i}:
                print(f"{indent}{(i, j)} determined to be 1")
                return 1
            print(f"{indent}calculating pascal number: {(i, j)}")
            result = nth_num(i-1, j-1, depth+1) + nth_num(i-1, j, depth+1)
            print(f"{indent}answer: {result}")
            return result
    
        result = [nth_num(i, n-1, 0) for i in range(n)]
        return result
    
    (0, 3) determined to be 1
    calculating pascal number: (1, 3)
      (0, 2) determined to be 1
      (0, 3) determined to be 1
    answer: 2
    calculating pascal number: (2, 3)
      calculating pascal number: (1, 2)
        (0, 1) determined to be 1
        (0, 2) determined to be 1
      answer: 2
      calculating pascal number: (1, 3)
        (0, 2) determined to be 1
        (0, 3) determined to be 1
      answer: 2
    answer: 4
    (3, 3) determined to be 1
    [1, 2, 4, 1]