【问题标题】:Get length of list in Python using recursion使用递归在Python中获取列表的长度
【发布时间】:2012-12-28 21:15:54
【问题描述】:

我正在尝试计算列表的长度。当我在 cmd 上运行它时,我得到:

RuntimeError:比较超过最大递归深度

我认为我的代码没有任何问题:

def len_recursive(list):
    if list == []:
        return 0
    else:
        return 1 + len_recursive(list[1:])

【问题讨论】:

  • 您要传递多长时间的列表?另外,不要命名变量list;它掩盖了内置类型。
  • 我确信这很明显,但如果有人在错误的上下文中遇到此问题,这是计算列表长度的一种非常糟糕的方法。使用len()。 (我假设 OP 这样做是出于学习目的)。
  • 您还没有回答一个非常重要的问题:您传递给函数的列表有多长?你可以使用len(l)来判断。
  • @A.R.S. : 目前,我正在使用一个包含 200 个元素的列表。由于这是作业,我不能使用 len(l)
  • @Malyk:200 不能用?我相信每个实现的默认递归限制至少为 1000,而且您的代码显然只进行 200 次递归调用,所以它应该可以工作。 (除非由于某些其他原因,调用堆栈深处已经有 800 个调用?)

标签: python list recursion


【解决方案1】:

除非您可以预测它不会太深,否则不要使用递归。 Python 对递归深度的限制非常小。

如果你坚持递归,有效的方法是:

def len_recursive(lst):
    if not lst:
        return 0
    return 1 + len_recursive(lst[1::2]) + len_recursive(lst[2::2])

【讨论】:

  • 可以通过sys.getrecursionlimit()获取当前递归深度限制
  • 此外,对于小的 n,OP 的解决方案可能会快得多,并且在您达到超出递归限制的 n 值之前不会明显变慢,所以我不确定“高效”在这里真的很合适。
  • @abarnert,但它允许超出递归限制。它只需要O(lg n) 堆栈深度。
  • 是的,固定版本是固定的。你是对的,它可以(至少在理论上)上升到接近 2**1000 而不仅仅是 1000,但这并不能使它更高效;它使它更正确。见pastebin.com/xiPzh0vK;在 64 位 Apple Python 2.7.2 中,除了 550-1350 之外,OP 的版本在每个范围内都比您的版本快——当然,除了在 1350 时它失败比您的成功快得多。您的代码显然更好;这只是你用来描述为什么更好的词。
【解决方案2】:

Python 中的递归深度是有限的,但可以增加,如post 所示。如果 Python 支持 tail call 优化,则此解决方案适用于任意长度的列表:

def len_recursive(lst):
    def loop(lst, acc):
        if not lst:
            return acc
        return loop(lst[1:], acc + 1)
    return loop(lst, 0)

但事实上,您将不得不使用较短的列表和/或增加允许的最大递归深度。

当然,没有人会在现实生活中使用这个实现(而不是使用 len() 内置函数),我猜这是递归的学术示例,但即便如此,最好的方法是使用迭代,如@poke's answer所示。

【讨论】:

  • 感谢奥斯卡。这是一项任务,我不使用 len() 函数。你的版本很好,但正如你提到的迭代方法,我也会做一个迭代版本
【解决方案3】:

正如其他人所解释的,您的函数存在两个问题:

  1. 它不是尾递归的,所以它只能处理列表只要sys.getrecursionlimit
  2. 即使是尾递归,Python 也不会进行尾递归优化。

第一个很容易解决。例如,请参阅 Óscar López 的回答。

第二个很难解决,但并非不可能。一种方法是使用协程(建立在生成器上)而不是子例程。另一种方法是不实际递归调用函数,而是返回具有递归结果的函数,并使用应用结果的驱动程序。有关如何实现后者的示例,请参阅 Paul Butler 的 Tail Recursion in Python,但这就是您的情况。

从 Paul Butler 的 tail_rec 函数开始:

def tail_rec(fun):
    def tail(fun):
        a = fun
        while callable(a):
            a = a()
        return a
    return (lambda x: tail(fun(x)))

这不能作为他的案例的装饰器,因为他有两个相互递归的函数。但在你的情况下,这不是问题。因此,使用 Óscar López 的版本:

@tail_rec
def tail_len(lst):
    def loop(lst, acc):
        if not lst:
            return acc
        return lambda: loop(lst[1:], acc + 1)
    return lambda: loop(lst, 0)

现在:

>>> print tail_len(range(10000))
10000

多田。

如果你真的想使用它,你可能想把tail_rec 变成一个更好的装饰器:

def tail_rec(fun):
    def tail(fun):
        a = fun
        while callable(a):
            a = a()
        return a
    return functools.update_wrapper(lambda x: tail(fun(x)), fun)

【讨论】:

    【解决方案4】:

    假设您正在使用一叠纸来运行此程序。你想计算你有多少张纸。如果有人给你 10 张纸,你拿第一张纸,把它放在桌子上,抓起下一张纸,放在第一张纸旁边。你这样做了 10 次,你的桌子已经满了,但你已经把每张纸都摆好了。然后你开始计算每一页,在你计数时回收它,0 + 1 + 1 + ... => 10。这不是计算页面的最佳方法,但它反映了递归方法和 python 的实现。

    这适用于少量页面。现在想象有人给了你 10000 张纸。很快,您的办公桌上就没有空间来布置每一页了。这基本上就是错误消息告诉您的内容。

    最大递归深度是表格可以容纳的“多少张纸”。每次调用 python 时都需要保留“1 + 递归调用的结果”,以便在所有页面都布置好后,它可以返回并计算它们。不幸的是,在最后的计数发生之前你的空间已经用完了。

    如果你想递归地学习,因为你想在任何合理的情况下使用 len(),只需使用小列表,25 应该没问题。

    如果支持tail calls,某些系统可以处理大型列表

    【讨论】:

      【解决方案5】:

      您的异常消息意味着您的方法被递归调用太频繁,因此您的列表可能太长而无法像这样递归地计算元素。不过,您可以简单地使用迭代解决方案:

      def len_iterative(lst):
          length = 0
          while lst:
              length += 1
              lst = lst[1:]
          return length
      

      请注意,这很可能仍然是一个糟糕的解决方案,因为lst[1:] 将继续创建列表的副本。所以你最终会得到len(lst) + 1 列表实例(长度为0len(lst))。直接使用内置的len 可能是最好的主意,但我想这是一个作业。

      【讨论】:

      • 或直接使用:sum(1 for _ in lst)
      • @poke:谢谢,是的,使用 len 函数更实用,但这是一项任务
      • @poke 不要忘记在最后返回长度!
      • @ÓscarLópez 哦,完全错过了!谢谢:D
      【解决方案6】:

      Python 没有优化尾递归调用,因此使用这种递归算法不是一个好主意。 您可以使用sys.setrecursionlimit() 调整堆栈,但这仍然不是一个好主意。

      【讨论】:

      • 反正不是尾号。
      猜你喜欢
      • 2019-10-29
      • 1970-01-01
      • 2013-12-07
      • 2011-08-07
      • 1970-01-01
      • 2022-01-27
      • 1970-01-01
      • 2020-02-06
      • 2012-01-30
      相关资源
      最近更新 更多