Может ли лямбда-функция вызывать себя рекурсивно в Python?

56

Регулярная функция может содержать вызов для себя в своем определении, без проблем. Я не могу понять, как это сделать с помощью лямбда-функции, хотя по той простой причине, что функция лямбда не имеет имени, на которое нужно ссылаться. Есть ли способ сделать это? Как?

  • 2
    Я испытываю желание пометить это, что, черт возьми, или ты не хочешь делать это. Почему бы вам просто не использовать обычную функцию?
  • 3
    Я хочу сделать, это запустить Reduce () на дереве. Лямбда прекрасно работает в одномерном списке, и рекурсия выглядела как естественный способ заставить ее работать на дереве. Тем не менее, настоящая причина в том, что я просто изучаю Python, поэтому я пинаю шины.
Показать ещё 4 комментария
Теги:
lambda
recursion
y-combinator

9 ответов

55
Лучший ответ

Единственный способ, который я могу придумать, - это дать функции имя:

fact = lambda x: 1 if x == 0 else x * fact(x-1)

или поочередно, для более ранних версий python:

fact = lambda x: x == 0 and 1 or x * fact(x-1)

Обновить: используя идеи из других ответов, мне удалось вставить факториальную функцию в одну неназванную лямбда:

>>> map(lambda n: (lambda f, *a: f(f, *a))(lambda rec, n: 1 if n == 0 else n*rec(rec, n-1), n), range(10))
[1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880]

Так что это возможно, но не рекомендуется!

  • 2
    map(lambda n: (lambda f, n: f(f, n))(lambda f, n: n*f(f, n-1) if n > 0 else 1, n), range(10))
  • 2
    Бесполезно и весело. Вот почему я люблю вычисления.
Показать ещё 8 комментариев
38

без уменьшения, отображение, имя lambdas или внутренние элементы python:

(lambda a:lambda v:a(a,v))(lambda s,x:1 if x==0 else x*s(s,x-1))(10)
  • 4
    Буду признателен, если вы объясните, как работает этот код :)
  • 2
    Поскольку первая функция и ее возвращаемое значение вызываются немедленно, они служат только в качестве присваиваний. Немного Pythonized, этот код говорит a = lambda myself, x: 1 if x==0 else x * myself(myself, x-1) то v = 10 и, наконец, a(a, v) . Сложная лямбда-функция предназначена для принятия себя в качестве первого параметра (поэтому я переименовала этот аргумент для myself ), который она использует для рекурсивного вызова себя.
21

Вы не можете напрямую это сделать, потому что у него нет имени. Но с помощью вспомогательной функции, такой как Y-combinator, на которую указывает Лемми, вы можете создать рекурсию, передав функцию как параметр самому себе (как это ни странно звучит):

# helper function
def recursive(f, *p, **kw):
   return f(f, *p, **kw)

def fib(n):
   # The rec parameter will be the lambda function itself
   return recursive((lambda rec, n: rec(rec, n-1) + rec(rec, n-2) if n>1 else 1), n)

# using map since we already started to do black functional programming magic
print map(fib, range(10))

Это печатает первые десять чисел Фибоначчи: [1, 1, 2, 3, 5, 8, 13, 21, 34, 55],

  • 0
    Думаю, я наконец понял, для чего нужен Y-комбинатор. Но я думаю, что в Python, как правило, было бы проще просто использовать «def» и дать функции имя ...
  • 0
    Забавно то, что ваш пример Фибоначчи является отличным примером чего-то более естественного для генератора. :-)
Показать ещё 2 комментария
18

В отличие от того, что сказал, вы МОЖЕТЕ это сделать прямо.

(lambda f: (lambda x: f(lambda v: x(x)(v)))(lambda x: f(lambda v: x(x)(v))))(lambda f: (lambda i: 1 if (i == 0) else i * f(i - 1)))(n)

Первая часть представляет собой комбинатор с фиксированной запятой Y, который облегчает рекурсию в исчислении лямбда

Y = (lambda f: (lambda x: f(lambda v: x(x)(v)))(lambda x: f(lambda v: x(x)(v))))

вторая часть - это факториальная функция, определенная рекурсивно

fact = (lambda f: (lambda i: 1 if (i == 0) else i * f(i - 1)))

Y применяется к факту, чтобы сформировать другое лямбда-выражение

F = Y(fact)

который применяется к третьей части, n, которая evaulating к n-м факториалу

>>> n = 5
>>> F(n)
120

или эквивалентно

>>> (lambda f: (lambda x: f(lambda v: x(x)(v)))(lambda x: f(lambda v: x(x)(v))))(lambda f: (lambda i: 1 if (i == 0) else i * f(i - 1)))(5)
120

Если вы предпочитаете фибры к фактам, вы можете сделать это тоже, используя тот же комбинатор

>>> (lambda f: (lambda x: f(lambda v: x(x)(v)))(lambda x: f(lambda v: x(x)(v))))(lambda f: (lambda i: f(i - 1) + f(i - 2) if i > 1 else 1))(5)
8
  • 7
    Отличная работа, вы только что превратили Python в Lisp :)
11

Да. У меня есть два способа сделать это, и один уже был покрыт. Это мой предпочтительный способ.

(lambda v: (lambda n: n * __import__('types').FunctionType(
        __import__('inspect').stack()[0][0].f_code, 
        dict(__import__=__import__, dict=dict)
    )(n - 1) if n > 1 else 1)(v))(5)
  • 6
    Я не знаю Python, но это выглядит ужасно. Там действительно должен быть лучший способ.
  • 2
    Ты должен научиться ценить неясный код, чувак. :(
Показать ещё 4 комментария
6

Я никогда не использовал Python, но этот, вероятно, вы ищете.

1

Этот ответ довольно простой. Это немного проще, чем ответ Хьюго Уолтера:

>>> (lambda f: f(f))(lambda f, i=0: (i < 10)and f(f, i + 1)or i)
10
>>>

Ответ Хьюго Уолтера:

(lambda a:lambda v:a(a,v))(lambda s,x:1 if x==0 else x*s(s,x-1))(10)
0

Ну, не совсем чистая лямбда-рекурсия, но она применима в тех местах, где вы можете использовать только лямбда, например. уменьшить, отобразить карты и список, или другие лямбды. Трюк состоит в том, чтобы воспользоваться пониманием списка и областью имен Python. Следующий пример пересекает словарь по данной цепочке ключей.

>>> data = {'John': {'age': 33}, 'Kate': {'age': 32}}
>>> [fn(data, ['John', 'age']) for fn in [lambda d, keys: None if d is None or type(d) is not dict or len(keys) < 1 or keys[0] not in d else (d[keys[0]] if len(keys) == 1 else fn(d[keys[0]], keys[1:]))]][0]
33

lambda повторно использует свое имя, определенное в выражении понимания списка (fn). Пример довольно сложный, но он показывает концепцию.

-3

Если бы вы были действительно мазохистскими, вы могли бы сделать это с помощью C-расширений, но для эха Грега (привет Грега!) это превосходит возможности лямбды (неназванного, анонимного) functon.

Нет. (для большинства значений no).

  • 5
    (> это превышает возможности лямбды) --- Нет, это не так. Комбинатор Y подобен самой известной из существующих абстрактных конструкций, и он делает это без каких-либо взломов.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню