Iteration count in recursive function

Question:

I am writing a recursive function to make permutations of digits from 0 to n. The program will return the th permutation that is obtained. It all works well but I had to use the cheap trick of defining count as a list, that is count=[0]. In this way I am using the properties of lists in order to properly update the variable count[0] at each iteration.

Ideally, what I would like to do is to define count as an integer number instead. However, this does not work because count is then updated only locally, within the scope of the function at the time it is called.

What is the proper way to count the iterations in a recursive function like this?

Below I show the code. It works, but I hate the way I am using count here.


import numpy as np

N=10
available=np.ones(N)

def permutations(array, count=[0], n=N, start=0, end=N, th=100):
    if count[0]<th:
        for i in range(n):
            if available[i]:                           
                array[start]=i
                if end-start>1:
                    available[i]=0
                    permutations(array, count, n, start+1, end)
                    available[i]=1
                else:
                    count[0]+=1
                    break
            if count[0]==th:
                a=''.join(str(i) for i in array)
                return a

def main():
    array=[0 for _ in range(N)]
    count=[0]
    print(permutations(array, count, N, start=0, end=N))

if __name__=="__main__":
    main()

Asked By: 3sm1r

||

Answers:

Not necessarily ideal but to answer the question, one could use a global variable as follows:

import numpy as np

N = 10
available = np.ones(N)
count = 0


def permutations(array, n=N, start=0, end=N, th=100):
    global count
    if count < th:
        for i in range(n):
            if available[i]:
                array[start] = i
                if end-start > 1:
                    available[i] = 0
                    permutations(array, n, start+1, end)
                    available[i] = 1
                else:
                    count += 1
                    break
            if count == th:
                return ''.join(str(i) for i in array)



def main():
    array = [0 for _ in range(N)]
    print(permutations(array, N, start=0, end=N))
    print(f'{count=}')


if __name__ == "__main__":
    main()

Output:

0123495786
count=100
Answered By: Pingu

Different ways to update a variable from other scopes… and each with its own advantages and disadvantages (performance, access to the variable, …):
with global approach (as Pingu did),
with nonlocal
and with function’s attribute.

The example under consideration, the factorial function, is merely illustrative but it can be easily readapted to your case.

def fact_global(n):
    global counter
    counter += 1
    if n == 1:
        return 1
    return n*fact_global(n-1)


def fact_nonlocal(n):
    counter = 0
    def __fact(n):
        nonlocal counter
        counter += 1
        
        if n == 1:
            return 1
        return n*__fact(n-1)
    return __fact(n)


def fact_attr(n):
    fact_attr.counter = 0
    def __fact(n):
        fact_attr.counter += 1
        
        if n == 1:
            return 1
        return n*__fact(n-1)
    return __fact(n)


n = 10
# case: global 
counter = 0
fact_global(n)
print('global', counter)

# case: nonlocal
fact_nonlocal(n)
import inspect
fr = inspect.currentframe()
print('nonlocal', fr.f_locals['counter']) # not recommended, just for curiosity!

# case: function's attribute
fact_attr(n)
print('attr', fact_attr.counter)

Retrieving the value of the variable under investigation is quite straightforward with a global-setting and with function’s attribute but not trivial (and not recommended) with nonlocal (inspect is more a debugging tool).

Here a partial result of the workbench:

n=860
fact_nonlocal(n):   2.60644 ± 0.00586
fact_global(n):     2.74698 ± 0.02283
fact_attr(n):       3.01219 ± 0.00539

The results are of the same order of magnitude (due to limitations of the host only tested with a maximum of n=860 so not reliable for an asymptotic conclusion), in this case it seems that it doesn’t really matter which one you choose but it is more important to focus on how you are going to access to the variable later in the program.

Answered By: cards