簡體   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