繁体   English   中英

计算递归调用 function 的次数(Python)

[英]Counting how many times a function is called recursively (Python)

我目前正在制作一个程序,其中包含一个总是返回 1 的递归 function。function 看起来像这样:

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)

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

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

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)

使用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}

这个 output 表示fn()被调用了七次。

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

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)

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

这类似于通过全局变量执行此操作,但具有将计数限制为 function scope 的额外好处。

例如:

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)

Output:

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

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

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

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))

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

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将返回调用次数。

简单的方法:

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)')  

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

如果你不能,你最好定义一个 function 用于定点运算符,如下所示:

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM