简体   繁体   English

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

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

I am currently making a program that includes a recursive function that always returns 1. The function looks like this:我目前正在制作一个程序,其中包含一个总是返回 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)

As I will be creating other functions, I need to create a function that counts how many times fn() is called recursively within the fn() function (How many calls does 'n' take to reach 1).由于我将创建其他函数,因此我需要创建一个 function 来计算在 fn() function 中递归调用 fn() 的次数(“n”需要多少次调用才能达到 1)。 I am able to do this using a global variable, however I am not sure how to do this using another recursive function.我可以使用全局变量来做到这一点,但是我不确定如何使用另一个递归 function 来做到这一点。 Any help would be appreciated.任何帮助,将不胜感激。 Thanks.谢谢。

Why not add a second parameter to your function and increment that on recursive calls?为什么不向您的 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)

Use cProfile foo.py:使用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]))

Then execute with:然后执行:

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}

This output indicates that fn() is called seven times.这个 output 表示fn()被调用了七次。

A solution is to add an optional parameter to your function which you can increment at each function's call.一种解决方案是向您的 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 an arguably simpler option is to hold the count state inside the function itself, without having to nest it nor wrap it in a decorator. FWIW 一个可以说是更简单的选择是将计数 state 保存在 function 本身内部,而不必嵌套它或将其包装在装饰器中。

This is akin to doing it thru a global variable, but with the added benefit of restricting the count to the function scope.这类似于通过全局变量执行此操作,但具有将计数限制为 function scope 的额外好处。

For instance:例如:

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: Output:

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

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

PS: Your n > 1 check is unnecessary. PS:您的n > 1检查是不必要的。 You might simplify your function a bit by dropping it altogether:您可以通过完全删除 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))

Maybe a decorator might be the solution here:也许装饰器可能是这里的解决方案:

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

Now, the code would look like现在,代码看起来像

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

(which is just the code but with an extra @count ) (这只是代码,但有一个额外的@count

fn(n) would return 1 or 1.0, and fn.call_count would return the number of calls. fn(n)将返回 1 或 1.0,而fn.call_count将返回调用次数。

The simply way:简单的方法:

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

This topic is not so obvious unless you can decorate your function (fully replacing the reference to a function in its definition scope).除非您可以装饰您的 function(在其定义范围内完全替换对 function 的引用),否则这个主题并不是那么明显。

If you can't , you better define a function for use with Fixed Point operator , like below:如果你不能,你最好定义一个 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

BTW such representation of a recursion allows you to control calls stack or even to translate recursive calls to a loops, great invention:)顺便说一句,这种递归表示允许您控制调用堆栈,甚至可以将递归调用转换为循环,伟大的发明:)

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

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