Рекурсия с тернарным оператором "hack" в Python
играя в 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, если c еще a" (он же "c?b: a") не работает:
g = lambda S,b : (g(S[1:],b),S)[len(S)==b]
Это превысит максимальную рекурсию глубина.
почему это не работает?
(Я знаю, что ни один из них не является примером отличного стиля кодирования, но это не относится к делу.)
3 ответов
Ok, давайте посмотрим на 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()
)
)
)]
)
как вы можете видеть, первое, что этот код выполняет при вызовах лямбда-это создание кортежа, и после этого непосредственно приходит рекурсивный вызов (Call(Name('g'...
) к той же лямбде.
вызов-это первое, что делается, и поскольку нарезка пустого списка по-прежнему пуста список:
>>>[1][1:]
[]
>>>[][1:]
[]
это означает, что g(S[1:])
уменьшит ваш список до пустого списка, а затем продолжит бесконечно вызывать g
С пустым списком. Это происходит из-за того, как парсер выполняет операторы. Первое, что выполняется, - это вызов рекурсивного метода, поэтому он не остановится.
моя точка зрения: базовый случай не работает над рекурсией.
надеюсь, это принесет больше света к теме.
Я думаю, что проблема связана с тем, как тернарный оператор работает. При использовании тернарного оператора оба выражения оцениваются перед проверкой условия.
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
если у вас A if C else B
, потом C
выполняется сначала, а потом одним из A
или B
выполняется (и это результат возвращается), тогда как если вы делаете [B, A][C]
, и A
и B
выполнено, а то C
. Вы можете легко проверить это, сделав p("A") if p("C") else p("B")
и [p("B"), p("A")][p("C")]
С функцией p
который печатает свой вход, а затем возвращает True
или False
Итак, в первом случае S if len(S)==b else f(S[1:],b)
, выполняется рекурсивный вызов только если условие не применяется. Во втором случае, однако, он выполняется прежде чем условие даже испытано, и то же самое в рекурсивно называемой функции ad infinitum.
(я предполагаю, что вы не собираетесь использовать это на практике, поэтому это может быть не важно, но в любом случае: обратите внимание, что (1) обе функции не имеют защиты для случая len(S) < b
, (2) то же самое может быть достигнуто с S[-b:]
и (3) Использование if/else
, конечно, много более читабельный.)