pythonooprecursionbacktrackingobject-oriented-analysis

explanation to this recursion problem in python


check this sample code for backtracking, there are two ways I can append the variable i to curr before backtracking, the one here (not commented) updates the global ans array, whereas the other way doesn't (shown below).:

n = 4
k = 2
ans = []
def backtrack(first, curr):
    if len(curr)==k:
        ans.append(curr)
    for i in range(first, n+1):
        # curr.append(i)
        backtrack(i+1, curr+[i])
        # curr.pop()

curr = []
backtrack(1, curr)
print("result = ",ans)

output here: result = [[1, 2], [1, 3], [1, 4], [2, 3], [2, 4], [3, 4]]

for the other way:

n = 4
k = 2
ans = []
def backtrack(first, curr):
    if len(curr)==k:
        ans.append(curr)
    for i in range(first, n+1):
        curr.append(i)
        backtrack(i+1, curr)
        curr.pop()

curr = []
backtrack(1, curr)
print("result = ",ans)

output here: result = [[], [], [], [], [], []]

I wish to understand, what exactly changes here and why the global output array ans behaves differently


Solution

  • curr+[i] creates a new object which gets passed to the next recursive call. So whenever you then append curr to ans with this version, you append a new object showing you a snapshot of what curr is which won't later get modified.

    When you instead use .append and .pop, you keep passing and having only one single object, so when you append it to ans it is really that unique object that you append everytime again and again. Then you will keep updating that unique object which will reflect on all element of ans since they are the same object. Given at the end of processing curr gets cleared you end up having only empty lists everywhere, even if at every step curr had actually the value you wanted.

    You can see that by adding this print first to check that apart from this bug, everything still works the same in both cases as you would expect:

    def backtrack(first, curr):
        print(first, id(curr), curr)
        # ... and the rest
    

    To fix the second version, you can pass a copy instead:

    curr.append(i)
    backtrack(i+1, curr.copy())
    curr.pop()
    

    or copy when you append to ans

    ans.append(curr.copy())