计算一个函数被递归调用的次数(Python)

时间:2021-03-15 21:43:43

标签: python

我目前正在制作一个程序,其中包含一个始终返回 1 的递归函数。 该函数如下所示:

def fn(n):
    if n <= 1:
        return n
    elif n > 1 and n % 2 == 0:
        return fn(n/2)
    elif n > 1 and n % 2 > 0:
        return fn(3*n+1)

因为我将创建其他函数,所以我需要创建一个函数来计算 fn() 函数内递归调用 fn() 的次数(“n”需要多少次调用才能达到 1)。我可以使用全局变量来做到这一点,但是我不确定如何使用另一个递归函数来做到这一点。任何帮助,将不胜感激。谢谢。

7 个答案:

答案 0 :(得分:2)

为什么不在您的函数中添加第二个参数并在递归调用时增加它?

def fn(n):
    def _fn(n, calls):
        if n <= 1:
            return n, calls
        # n > 1 is a given by this point.
        return _fn(n / 2 if n % 2 == 0 else 3 * n + 1, calls + 1)

    return _fn(n, 1)

答案 1 :(得分:1)

使用cProfile foo.py:

def fn(n):
    if n <= 1:
        return n
    elif n > 1 and n % 2 == 0:
        return fn(n/2)
    elif n > 1 and n % 2 > 0:
        return fn(3*n+1)


if __name__ == "__main__":
    import sys
    fn(int(sys.argv[1]))

然后执行:

python -m cProfile foo.py 10
         10 function calls (4 primitive calls) in 0.000 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000    0.000    0.000 foo.py:1(<module>)
      7/1    0.000    0.000    0.000    0.000 foo.py:1(fn)
        1    0.000    0.000    0.000    0.000 {built-in method builtins.exec}
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}

此输出表明 fn() 被调用了七次。

答案 2 :(得分:1)

一个解决方案是向您的函数添加一个可选参数,您可以在每个函数调用时增加该参数。

def fn(n, ncalls=1):
    if n <= 1:
        return n, ncalls
    elif n > 1 and n % 2 == 0:
        return fn(n/2, ncalls + 1)
    elif n > 1 and n % 2 > 0:
        return fn(3*n+1, ncalls + 1)

答案 3 :(得分:1)

FWIW 一个可以说更简单的选择是将计数状态保存在函数本身内部,而不必将其嵌套或包装在装饰器中。

这类似于通过全局变量执行此操作,但具有将计数限制在函数范围内的额外好处。

例如:

def fn(n):
    try:
        fn.count += 1
    except AttributeError:
        fn.count = 1

    if n <= 1:
        return n
    elif n > 1 and n % 2 == 0:
        return fn(n/2)
    elif n > 1 and n % 2 > 0:
        return fn(3*n+1)

输出:

In [15]: fn(5)
Out[15]: 1.0

In [16]: fn.count
Out[16]: 6

PS:您的 n > 1 检查是不必要的。您可以通过完全删除它来简化您的功能:

def fn(n):
    try:
        fn.count += 1
    except AttributeError:
        fn.count = 1

    if n <= 1:
        return n
    return fn((3*n + 1) if n % 2 else (n / 2))

答案 4 :(得分:1)

也许装饰器可能是这里的解决方案:

def count(func):
    def counted(value):
        counted.call_count += 1
        return func(value)
    counted.call_count = 0
    return counted

现在,代码看起来像

@count
def fn(n):
    if n <= 1:
        return n
    elif n > 1 and n % 2 == 0:
        return fn(n/2)
    elif n > 1 and n % 2 > 0:
        return fn(3*n+1)

(这只是代码,但带有额外的 @count

fn(n) 将返回 1 或 1.0,而 fn.call_count 将返回调用次数。

答案 5 :(得分:0)

简单的方法:

ncalls=0
def fn(n):
    global ncalls
    ncalls +=1
    if n <= 1:
        return n
    elif n > 1 and n % 2 == 0:
        return fn(n/2)
    elif n > 1 and n % 2 > 0:
        return fn(3*n+1)

if __name__ == "__main__":
    n = 10
    print(f'fn({n}) = {fn(n)}, {ncalls} function call(s)')  

答案 6 :(得分:0)

除非您可以修饰您的函数(在其定义范围内完全替换对函数的引用),否则这个主题并不那么明显。

如果不能,您最好定义一个与 Fixed Point operator 一起使用的函数,如下所示:

In [21]: import itertools, functools
    ...: def fn(recurse, n):
    ...:     if n <= 1:
    ...:         return n
    ...:     elif n > 1 and n % 2 == 0:
    ...:         return recurse(n/2)
    ...:     elif n > 1 and n % 2 > 0:
    ...:         return recurse(3*n+1)
    ...:
    ...:
    ...: def fix(f, *args, **kwargs):
    ...:     def g(*args, **kwargs):
    ...:         return f(g, *args, **kwargs)
    ...:     return g
    ...:
    ...: def count_calls(fn, *args, **kwargs):
    ...:     cnt = itertools.count()
    ...:     def wrapper(recurse, *args, **kwargs):
    ...:         cnt.__next__()
    ...:         return fn(recurse, *args, **kwargs)
    ...:     fix(wrapper)(*args, **kwargs)
    ...:     return next(cnt)
    ...:
    ...:
    ...: print(fix(fn)(10))
    ...: count_calls(fn, 10)
1.0
Out[21]: 7

顺便说一句,递归的这种表示允许您控制调用堆栈,甚至将递归调用转换为循环,伟大的发明:)