Рекурсія з використанням врожаю


84

Чи є спосіб змішати рекурсію та yieldвисловлювання? Наприклад, генератор нескінченних чисел (з використанням рекурсії) буде приблизно таким:

def infinity(start):
    yield start
    # recursion here ...

>>> it = infinity(1)
>>> next(it)
1
>>> next(it)
2

Я намагався:

def infinity(start):
    yield start
    infinity(start + 1)

і

def infinity(start):
    yield start
    yield infinity(start + 1)

Але ніхто з них не зробив того, що я хочу, перший зупинився після того, як він поступився, startа другий поступився start, потім генератор, а потім зупинився.

ПРИМІТКА: Будь ласка, я знаю, що ви можете зробити це за допомогою циклу while:

def infinity(start):
    while True:
        yield start
        start += 1

Я просто хочу знати, чи можна це робити рекурсивно.


Див. [Тут] [1], щоб отримати гарну відповідь на це питання, яке я задавав деякий час тому. [1]: stackoverflow.com/questions/5704220 / ...
sizzzzlerz

Примітка: правильним способом зробити це буде використання, itertools.countа не прокрутка власного рішення, на основі циклу чи іншого.
Петро Вікторін

8
@PetrViktorin це лише приклад, генерування нескінченних чисел - це зовсім не справжня проблема
juliomalegria

Відповіді:


157

Так, ви можете зробити це:

def infinity(start):
    yield start
    for x in infinity(start + 1):
        yield x

Однак це призведе до помилки після досягнення максимальної глибини рекурсії.

Починаючи з Python 3.3, ви зможете використовувати

def infinity(start):
    yield start
    yield from infinity(start + 1)

Якщо ви просто викликаєте свою функцію генератора рекурсивно, не зациклювавшись над нею або не yield fromввімкнувши її, все, що вам потрібно зробити, це створити новий генератор, фактично не запускаючи тіло функції і не даючи нічого.

Детальніше див. У документі PEP 380 .


13
Але, схоже yield from, все ще існує межа рекурсії :(
Джо Со

3
завжди буде межа рекурсії
Радіокерований

12

У деяких випадках може бути кращим використовувати стек замість рекурсії для генераторів. Має бути можливість переписати рекурсивний метод, використовуючи стек і цикл while.

Ось приклад рекурсивного методу, який використовує зворотний виклик і може бути переписаний за допомогою логіки стека:

def traverse_tree(callback):
    # Get the root node from somewhere.
    root = get_root_node()
    def recurse(node):
        callback(node)
        for child in node.get('children', []):
            recurse(child)
    recurse(root)

Вищевказаний метод перетинає дерево вузлів, де кожен вузол має childrenмасив, який може містити дочірні вузли. Коли зустрічається кожен вузол, видається зворотний виклик і поточний вузол передається йому.

Метод можна використовувати таким чином, роздруковуючи деякі властивості на кожному вузлі.

def callback(node):
    print(node['id'])
traverse_tree(callback)

Замість цього використовуйте стек і запишіть метод обходу як генератор

# A stack-based alternative to the traverse_tree method above.
def iternodes():
    stack = [get_root_node()]
    while stack:
        node = stack.pop()
        yield node
        for child in reversed(node.get('children', [])):
            stack.append(child)

(Зауважте, що якщо ви хочете той самий порядок обходу, що і спочатку, вам потрібно змінити порядок дочірніх, оскільки перша дочірня частина, додана до стеку, буде останньою, яка з’явиться)

Тепер ви можете отримати таку ж поведінку, як traverse_treeвище, але з генератором:

for node in iternodes():
    print(node['id'])

Це не універсальне рішення, але для деяких генераторів ви можете отримати хороший результат, замінивши обробку стека на рекурсію.


3
Приємна відповідь! Вихід у python 2.7 насправді не можна використовувати з рекурсією, але, керуючи стеком вручну, ви можете отримати той самий ефект.
00 Прометей

0
def lprint(a):
    if isinstance(a, list):
        for i in a:
            yield from lprint(i)
    else:
        yield a

b = [[1, [2, 3], 4], [5, 6, [7, 8, [9]]]]
for i in lprint(b):
    print(i)

Що це таке b? Намагайтеся не залишати відповіді лише на код ...
Невеликі

for i in lprint (a): print (i)
Юрий Блинков

Чому б не відредагувати відповідь, щоб вона була зрозумілішою? Ви можете зробити це, натиснувши маленький editтег під вашою відповіддю або натисніть тут . Крім того, як я вже сказав, спробуйте додати невелике пояснення того, як і чому це вирішує проблему
Tomerikoo

-3

Отже, вам просто потрібно додати цикл for там, де вам потрібно викликати свою функцію рекурсивно . Це стосується Python 2.7.


1
хоча ця відповідь вимагає більш детальної інформації, вона насправді відповідає прийнятій відповіді Свена Марнаха, див. його перший
штрих
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.