【问题标题】:Recursion with ternary operator 'hack' in Python在 Python 中使用三元运算符“hack”进行递归
【发布时间】:2013-11-20 12:12:06
【问题描述】:

在 Python 中玩耍,我发现以下代码按我的预期工作:

f = lambda S,b : (S if len(S)==b else f(S[1:],b))

从列表 S 中递归删除第一个元素,直到 S 的长度等于 b。例如 f([1,2,3,4,5,6],3) = [4,5,6]。

但是,令我惊讶的是,以下使用“三元破解”[a,b][c] 而不是“b if c else a”(又名“c?b:a”)的解决方案不起作用:

g = lambda S,b : (g(S[1:],b),S)[len(S)==b]

这将超过最大递归深度。

为什么这不起作用?

(我知道这两者都不是优秀的编码风格的例子,但现在这无关紧要。)

【问题讨论】:

  • 在第二种情况下,计算元组的两个元素,然后选择其中一个。这意味着递归调用总是发生!
  • 请注意,该函数在len(S) < b 的情况下缺乏保护措施,即它会递归地从列表中删除,即使它为空,也会导致对g 的无限递归调用。
  • +1 三元 hack 对我来说是新的 :)
  • 我刚刚了解到以下代码确实有效的有趣事实:g = lambda S,b : (lambda : g(S[1:],b), lambda : S)[len(S)==b]()

标签: python recursion conditional-operator


【解决方案1】:

好的,让我们看看 lambda 函数生成的ast

import ast
tree = ast.parse('lambda S,b : (g(S[1:],b),S)[len(S)==b]')
ast.dump(tree)

在 vim 中完成一些格式化后,这是我得到的:

Module(
  [Expr(
    Lambda(
      arguments(
        [Name('S', Param()), Name('b', Param())],
        None,
        None,
        []
      ),
      Subscript(
        Tuple(
          [Call(
              Name('g', Load()),
              [Subscript(Name('S', Load()), Slice(Num(1), None, None), Load()), Name('b', Load())],
              [],
              None,
              None
            ),
            Name('S', Load())
          ],
          Load()
        ),
        Index(
          Compare(
            Call(Name('len', Load()), [Name('S', Load())], [], None, None),
            [Eq()],
            [Name('b', Load())]
          )
        ),
        Load()
      )
    )
  )]
)

如您所见,此代码在调用 lambda 时执行的第一件事是创建元组,然后直接对同一 lambda 进行递归调用 (Call(Name('g'...)。

调用是完成的第一件事,因为空列表的切片仍然是空列表:

>>>[1][1:]
[]
>>>[][1:]
[]

这意味着g(S[1:]) 将减少您的列表直到空列表,然后继续无休止地使用空列表调用g。发生这种情况是因为解析器执行语句的方式。首先执行的是递归方法调用,所以它不会停止。

我的观点:基本情况不适用于递归。

希望这能为主题带来更多亮点。

【讨论】:

  • 得到了三个有用的答案,他们都很感激;我接受了这个,因为“ast”技巧可能对 python 中的其他奇怪遭遇有所帮助。
【解决方案2】:

我认为问题与三元运算符的工作方式有关。当您使用三元运算符时,两个表达式都会在检查条件之前进行评估。

g = lambda S,b : (g(S[1:],b),S)[len(S)==b]

所以在这种情况下,g(S[1:],b) 在到达 if 语句之前就被评估了。

如果您有函数,则没有与g(S[1:],b) 相同的基本情况

def func(S, b)
    return func(S[1:],)

func(S,b)
#output: error - exceed maximum recursion depth

S[1:] 将到达它为空的点,如果它为空,它将返回一个空列表。

一个关于空列表的小例子:

S = [0, 1]

S = S[1:]
# [1]

S = S[1:]
# [] # empty

S = S[1:]
# [] # also empty

【讨论】:

    【解决方案3】:

    如果您执行A if C else B,则首先执行C,然后执行其中一个 AB(并返回结果),而如果您执行@ 987654325@,AB 都被执行,然后 C。您可以通过使用 p 打印其输入然后返回 TrueFalse 的函数 [p("B"), p("A")][p("C")] 来轻松检查这一点。

    因此,在您的第一种情况下,S if len(S)==b else f(S[1:],b),仅当条件不适用时才会执行递归调用。然而,在第二种情况下,它甚至在测试条件之前就被执行,在递归调用的函数中也是如此,ad infinitum。

    (我假设您不打算在实践中使用它,因此这可能并不重要,但无论如何:请注意(1)这两个功能都缺乏针对len(S) < b的情况的保障,(2)同样可以使用S[-b:] 实现,(3) 使用if/else 当然,更具可读性。)

    【讨论】:

    • 我想到的实际用途是我为自己设置的一项挑战,即在一个语句中编写某种算法。该算法包括嵌套的 while-if 结构。所以我想到了结合三元运算符的递归作为我的出路:-)
    猜你喜欢
    • 2020-08-02
    • 2016-09-09
    • 1970-01-01
    • 2012-08-08
    • 2013-04-25
    • 1970-01-01
    • 1970-01-01
    • 2012-03-25
    • 2019-04-28
    相关资源
    最近更新 更多