【问题标题】:Getting None while reversing list in python在python中反转列表时获取无
【发布时间】:2022-01-24 12:50:25
【问题描述】:

代码很简单,但它返回“无”,我不知道为什么。有人可以更正我的代码吗?

def rev(x):
    global y
    if len(x)==0:
        return y

    else:
        y=y+[x[-1]]
        x.pop(-1) #Remove last item from list
        rev(x)

z=[1,2,3]
y=[]
print(rev(z))

【问题讨论】:

  • 因为对于len(x) != 0 和python 中的隐式返回为None 的情况,您不会返回任何内容。
  • 您的 else 子句不返回任何内容。
  • 你能更正代码吗?
  • 如果你想反转一个列表,你也可以使用l = [1, 2, 3, 4] -> l[::-1] -> l = [4, 3, 2, 1]
  • 尽管有以前的 cmets,我想知道你为什么要这样做。这是递归的家庭作业,还是您只想在不使用传统切片技术的情况下反转列表?

标签: python list recursion


【解决方案1】:

谢谢,但我应该使用递归...

递归是一种函数式遗产,因此将其与函数式风格一起使用会产生最佳效果。这意味着要避免诸如突变、变量重新分配和其他副作用之类的事情。

  1. 如果输入t为空,则返回空结果
  2. (归纳)t 至少有一个元素。将子问题 rev(t[1:0) 的结果添加到第一个元素 [t[0]] 的单例列表中
def rev(t):
  if not t:
    return []            # 1. empty t
  else:
    rev(t[1:]) + [t[0]]  # 2. at least one element

没有副作用的函数对于相同的输入总是会返回相同的结果。这使我们能够对其输出进行替换和函数调用,并轻松可视化其计算过程 -

rev([1,2,3,4,5])
== rev([2,3,4,5]) + [1]
== rev([3,4,5]) + [2] + [1]
== rev([4,5]) + [3] + [2] + [1]
== rev([5]) + [4] + [3] + [2] + [1]
== rev([]) [5] + [4] + [3] + [2] + [1]
== [] + [5] + [4] + [3] + [2] + [1]
== [5] + [4] + [3] + [2] + [1]
== [5,4] + [3] + [2] + [1]
== [5,4,3] + [2] + [1]
== [5,4,3,2] + [1]
== [5,4,3,2,1]

注意未决计算的金字塔形状。随着输入的增长,等待合并的单例列表的数量也在增长。如果t 非常大,则会导致堆栈溢出。

让我们看看一个小小的调整如何对流程产生重大影响 -

def rev(t, r = []):
  if not t:
    return r               # 1. empty t, return r
  else:
    rev(t[1:], [t[0]] + r) # 2. at least one element, prepend to r

我们没有将待处理的计算留给我们返回,而是引入了具有默认值的第二个参数。请注意下面的可视化过程如何保持线性和平坦,而不是像金字塔一样增长 -

rev([1,2,3,4,5])
== rev([2,3,4,5], [1])
== rev([3,4,5], [2,1])
== rev([4,5], [3,2,1])
== rev([5], [4,3,2,1])
== rev([], [5,4,3,2,1])
== [5,4,3,2,1]

当递归调用是函数的最后调用时,称为tail recursive。由于递归是函数式语言中的自然循环机制,编译器会检测到这些尾调用并有效地防止堆栈增长,称为尾调用消除

myinput = "abcdefghijklmnopqrstuvwxyz"
print("".join(rev(myinput)))
print("".join(rev(myinput * 100)))
zyxwvutsrqponmlkjihgfedcba
RecursionError: maximum recursion depth exceeded

那么为什么 Python 无法计算 rev(myinput * 100)?虽然 Python 支持许多功能特性,例如模块和一等函数,但它不提供尾调用消除,因此我们上面的 rev 函数仍然会溢出堆栈。结合其相对较小的堆栈大小,这一事实使 Python 成为真正学习递归的绝佳语言。了解其局限性后,您将了解编写高效算法意味着什么,甚至会了解堆栈安全的无限递归在其他语言中是如何实现的。

例如,这是一种通过使用 Python 的 inspect 模块实现无限递归的技术,该模块允许我们比较堆栈帧数据。递归函数转换为while 循环,当不再检测到尾调用时,返回最终结果。 Reza Bagheri 在this article 中提供了该技术的详细解释-

# tail_rec.py
import inspect

def tail_rec(func):
  is_rec = False
  t = []
  def loop(*args):
    nonlocal is_rec
    nonlocal t
    f = inspect.currentframe()
    if f.f_back:
      if f.f_back.f_back:
        if f.f_code == f.f_back.f_back.f_code:
          is_rec = True
          t = args
          return 
    while True:
      try:
        result = func(*args)
      except TypeError:
        raise Exception("function is not tail-recursive")
      if is_rec:
        is_rec = False
        args = t
      else:
        return result 
  return loop

要启用优化,我们只需将 tail_rec 装饰器添加到我们的函数中 -

from tail_rec import tail_rec    # <- import

@tail_rec                        # <- enable tail call elimination
def rev(t, r = []):
  if not t:
    return r
  else:
    return rev(t[1:], [t[0]] + r)

突然间,Python 递归限制消失得无影无踪 -

myinput = "abcdefghijklmnopqrstuvwxyz"
print("".join(rev(myinput * 100)))
zyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcba

【讨论】:

  • 我投了赞成票,因为我同意这个答案中所说的一切,而且非常清楚且解释清楚。但我会注意到 python 是一种非常不适合学习递归的语言。这个函数rev 将具有二次复杂度,因为每个+ 操作都会完全重建列表以生成新列表。
  • 学习者必须首先能够思考/可视化递归过程,然后才能对其进行空间/时间分析。简单的实现具有二次复杂性这一事实本身就是加深您对递归理解的入门课程。我更新了我的答案来解决这个问题。
  • 也许我不应该说什么!答案已经很好了。现在它很长,这意味着阅读它的人更难记住其中的重要信息;-)
  • 另外,现在你有一个带有可变默认参数的函数 r = [],这会在 python 中引起它自己的问题。
  • 感谢您的反馈,Stef。教授函数式风格很困难,因为它的全部好处只有在应用于整个系统时才能实现。通过针孔查看它会产生许多类似这样的对话。 r = [] 等默认参数不会引起问题。默认参数的变异会导致问题,但真正的函数式风格会避免任何可观察到的变异。问题化为虚无。
【解决方案2】:

这是一个示例,说明如何在不进行切片或递归的情况下原位反转列表的内容(并不是说你想这样做):

def rev(x):
    i = 0
    j = len(x)
    while (j := j - 1) > i:
        x[i], x[j] = x[j], x[i]
        i += 1
    return x

print(rev([1,2,3,4,5]))

【讨论】:

  • 谢谢,但我应该使用递归,这应该用于章节练习结束。
  • 我不认为while (j := j - 1) &gt; i: 是一个非常有教育意义的建议;-) 我建议将条件j - 1 &gt; i 和作业j -= 1 分开。
猜你喜欢
  • 1970-01-01
  • 2014-03-19
  • 1970-01-01
  • 1970-01-01
  • 2016-12-10
  • 2016-03-09
  • 2021-09-21
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多