【问题标题】:How to count the number of times a value recurs in a recursive function?如何计算一个值在递归函数中重复出现的次数?
【发布时间】:2017-01-07 00:58:31
【问题描述】:

如果你有递归函数(例如斐波那契数列):

def fib(n):
    """Return Fibonacci of n; assumes n is an int >= 0."""
    if n == 0 or n == 1:
        return 1
    else:
        return fib(n-1) + fib(n-2)

如何计算,例如,当 fib(20) 被调用时 fib(2) 出现的次数?

【问题讨论】:

  • 你可以在你的函数中实现一个作为变量传递的计数器,或者每次修改一个全局变量......
  • 看来我误读了您的问题...我将递归读作递归...对不起
  • 在计算fib(n) 时,您想计算每个ifib(i) 的调用次数,对吗?
  • global num_of_twos; if n == 2: num_of_twos+=1
  • @Natecat 完全...

标签: python recursion combinatorics


【解决方案1】:

你可以使用装饰器:

import functools

def count(f):
    """Count function calls."""
    @functools.wraps(f)
    def counted(*args, **kwargs):
        counted.count_calls += 1
        return f(*args, **kwargs)
    counted.count_calls = 0
    return counted

fib = count(fib)
fib(5)
fib.count_calls
# 15

或者,您现在可以使用此装饰器和@ 符号添加任何函数定义:

@count
def fib(n):
    ...

fib(5)
fib.count_calls
# 15

注意,这个装饰器会累积函数调用:

fib(5)
fib(5)
fib.count_calls
# 30

这是一个巧妙的实现,它利用了鲜为人知的function attributes。请注意,原始装饰器是从 John DiNero 的 count 函数修改而来的,他在他的 lecture on Memoization 中讨论了这个特定问题。

【讨论】:

  • 虽然我同意这个实现很聪明,但应该注意的是,它的副作用是——至少——使原来的函数调用开销增加了一倍。
  • 感谢@martineau 的评论。澄清一下,您是说每次调用 fib 也会对 count 进行整体调用,这会增加对堆栈的调用总数,而不是对 count_calls 的调用?
  • 不完全是。我的意思是,函数的修饰版本调用原始函数调用原始函数调用的修饰函数,等等——这是原始函数调用自身时函数调用次数的两倍。使用全局变量或传递额外的参数可能会比这更快。
  • @martineau 是的,这就是我的解释,也是关于性能的一个好点。感谢您指出这一点。
【解决方案2】:

您可以使用字典来计算对fib 的所有调用。在第一次调用 fib 之前必须清除字典。

calls = defaultdict(int)

在函数中,在执行任何其他操作之前更新字典中的相应条目:

def fib(n):
  global calls
  """Assumes n an int >= 0
     Returns Fibonacci of n"""
  calls[n] += 1
  if n == 0 or n == 1:
    return 1
  else:
    return fib(n-1) + fib(n-2)

【讨论】:

  • 除非它已被更改,否则这不是您使用 defaultdict 的方式。应该是calls = defaultdict(int)。还可能想提一下你必须from collections import defaultdict
  • @Natecat 相同的区别:0 是int() 的实例。
  • Except 0 不可调用,而函数 int 可调用。
  • 你也可以使用Counter
【解决方案3】:

这是我尝试过的......认为工作正常

def fib(n):
    global counter
    if (n == 0 or n == 1):
        counter=counter+1
        return 1
    else:
        counter=counter+1
        return fib(n-1) + fib(n-2)
def countfib(n):
    global counter
    counter = 0
    fib(5);
    global count
    count=counter
    counter = 0
    return count
counter=0
count=0
print fib(5)
count=countfib(5)
print count

输出:

8
15

【讨论】:

    【解决方案4】:

    我不清楚您要计算的重复值到底是​​什么,所以我猜这是使用相同参数(或一组参数,如果有超过)调用(递归)函数的次数一)。

    在下面的代码中,一个名为tally_recurring_args() 的装饰器用于将函数包装在一些代码中以执行此操作。为了简化事情并避免重新发明轮子,collections.Counter 用于计算对函数的每个唯一参数组合的调用次数。这是函数的一个属性,因此可以在每次调用装饰函数时在wrapper 中轻松引用它。

    import collections
    import functools
    
    def tally_recurring_args(func):
        func._args_counter = collections.Counter()  @ add attribute to func
    
        @functools.wraps(func)
        def wrapper(*args):
            key = ', '.join(str(arg) for arg in args)
            func._args_counter[key] += 1
            return func(*args)
    
        return wrapper
    
    @tally_recurring_args
    def fib(n):
        """Return Fibonacci of n; assumes n is an int >= 0."""
        if n == 0 or n == 1:
            return 1
        else:
            return fib(n-1) + fib(n-2)
    
    print('fib(5) -> {}'.format(fib(5)))
    for args, count in sorted(fib._args_counter.items()):
        print('fib({}): called {} time{}'.format(args, count,
                                                 's' if count > 1 else ''))
    

    输出:

    fib(5) -> 8
    fib(0): called 3 times
    fib(1): called 5 times
    fib(2): called 3 times
    fib(3): called 2 times
    fib(4): called 1 time
    fib(5): called 1 time
    

    【讨论】:

      【解决方案5】:

      怎么样:

      def fib(n, counts_dict):
          counts_dict[n] += 1
          if n == 0 or n == 1:
              return 1
          else:
              return fib(n-1, counts_dict) + fib(n-2), counts_dict
      

      在哪里counts_dict = collections.defaultdict(int)

      【讨论】:

      • @Natecat 似乎是个好主意,但是调用者将没有对它的引用,也无法访问计数。
      【解决方案6】:
      def fib(n, x):
          c = 1 if n == x else 0
      
          if n == 0 or n == 1:
              return 1, c
          else:
              fA, cA = fib(n - 1, x)
              fB, cB = fib(n - 2, x)
              return fA + fB, cA + cB + c
      

      如果我没有弄乱逻辑,这个函数接受nx 并返回一个元组(y, c) s.t。在计算过程中,fib(n, _)=yfib(x, _) 被调用了 c 次。

      对于涉及更新字典的其他提议的解决方案,这是一种更纯粹的替代方案。它基于这样的假设,即 OP 只需要为一个特定的 k 调用 f(k, _) 的次数,因此避免填充只需要一个值的字典。

      【讨论】:

        【解决方案7】:

        没有全局变量:

        from collections import defaultdict
        def fib(n, count=None):
            if count is None:
                count = defaultdict(int)
            count[n] += 1
            if n in (0, 1):
                return 1, count
            return fib(n - 1)[0] + fib(n - 2)[0], count
        

        fib 函数现在返回一个元组:第一个元素是所需值,第二个元素是一个字典,其中包含每个值调用 fib 函数的次数的信息。

        【讨论】:

          【解决方案8】:

          如何使用函数属性。就像一个静态变量。

          def fib(n): 
              """Return Fibonacci of n; assumes n is an int >= 0."""
              If hasattr(fib, 'cnt'):
                  fib.cnt += 1
              else:
                  fib.cnt = 1
              if n == 0 or n == 1: 
                  return 1 
              else: 
                  return fib(n-1) + fib(n-2)
          
          fib.cnt =0
          

          【讨论】:

            【解决方案9】:

            使用全局:

            c = 0
            def fib(n):
                global c
                c += 1
                if n == 1:
                    return 0
                elif n == 2:
                    return 1
                else:
                    return fib(n-1) + fib(n-2)
            

            【讨论】:

              【解决方案10】:

              这是另一种没有全局变量的解决方案,但通过值传递完成:

              def fibo(n, c):
              if n <= 2 and n > 0:
                  return n-1, c
              else:
                  n1, c1 = fibo(n-1,c+1)
                  n2, c2 = fibo(n-2,1)
                  return n1+n2, c1+c2
              

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 2022-01-06
                • 2012-10-25
                • 2017-04-14
                • 2015-10-28
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多