Подтвердить что ты не робот

Могут ли генераторы быть рекурсивными?

Я наивно пытался создать рекурсивный генератор. Не работает. Это то, что я сделал:

def recursive_generator(lis):
    yield lis[0]
    recursive_generator(lis[1:])

for k in recursive_generator([6,3,9,1]):
    print(k)

Все, что я получил, было первым элементом 6.

Есть ли способ заставить такой код работать? По существу передача команды yield на уровень выше в схеме рекурсии?

4b9b3361

Ответ 1

Попробуйте следующее:

def recursive_generator(lis):
    yield lis[0]
    yield from recursive_generator(lis[1:])

for k in recursive_generator([6,3,9,1]):
    print(k)

Я должен указать, что это не работает из-за ошибки в вашей функции. Вероятно, он должен включать проверку того, что lis не пуст, как показано ниже:

def recursive_generator(lis):
    if lis:
        yield lis[0]
        yield from recursive_generator(lis[1:])

Если вы находитесь на Python 2.7 и не имеете yield from, проверьте этот вопрос.

Ответ 2

Почему ваш код не выполнял работу

В вашем коде функция генератора:

  • возвращает (дает) первое значение списка
  • то он создает новый объект итератора, вызывающий одну и ту же функцию генератора, передавая ему фрагмент списка
  • а затем останавливается

Второй экземпляр итератора, который рекурсивно создан, никогда не повторяется. Вот почему вы получили только первый элемент списка.

Функция генератора полезна для автоматического создания объекта-итератора (объекта, который реализует

Примечание. элементы возвращаются в обратном порядке, поэтому вы можете использовать some_list.reverse() перед вызовом генератора в первый раз.

В этом примере важно отметить: функция генератора рекурсивно вызывает себя в цикле for, который видит итератор и автоматически использует на нем протокол итерации, поэтому он фактически получает значения из он.

Это работает, но Я думаю, что это действительно не полезно. Мы используем функцию генератора для итерации по списку и просто получаем элементы, по одному, но... список является итерабельным, поэтому нет необходимости в генераторах! Конечно, я понимаю, это всего лишь пример, возможно, есть полезные приложения этой идеи.

Другой пример

Позвольте переработать предыдущий пример (для ленивости). Допустим, нам нужно напечатать элементы в списке, добавив к каждому элементу количество предыдущих элементов (просто случайный пример, необязательно полезный).

Код:

def recursive_generator(some_list):
    """
    Return some_list items, one at a time, recursively iterating over a slice of it...
    and adding to every item the count of previous items in the list
    """
    if len(some_list)>1:
    # some_list has more than one item, so iterate over it
        for i in recursive_generator(some_list[1:]):
            # recursively call this generator function to iterate over a slice of some_list.
            # return one item from the list, but add 1 first. 
            # Every recursive iteration will add 1, so we basically add the count of iterations.
            yield i + 1
        else:
            # the iterator returned StopIteration, so the for loop is done.
            # to finish, return the only value not included in the slice we just iterated on.
            yield some_list[0]
    else:
        # some_list has only one item, no need to iterate on it.
        # just return the item.
        yield some_list[0]

some_list = [6,3,9,1]
for k in recursive_generator(some_list):
    print(k)

Теперь, как вы можете видеть, функция-генератор фактически что-то делает, прежде чем возвращать элементы списка, и использование рекурсии начинает иметь смысл. Тем не менее, просто глупый пример, но вы поняли идею.

Примечание:. Конечно, в этом глупом примере список должен содержать только числа. Если вы действительно хотите попробовать попробовать и разбить его, просто введите строку в some_list и получайте удовольствие. Опять же, это всего лишь пример, а не производственный код!

Ответ 3

Рекурсивные генераторы полезны для обхода нелинейных структур. Например, пусть двоичное дерево имеет значение None или кортеж значения, левое дерево, правое дерево. Рекурсивный генератор - это самый простой способ посетить все узлы. Пример:

tree = (0, (1, None, (2, (3, None, None), (4, (5, None, None), None))),
        (6, None, (7, (8, (9, None, None), None), None)))

def visit(tree):  # 
    if tree is not None:
        try:
            value, left, right = tree
        except ValueError:  # wrong number to unpack
            print("Bad tree:", tree)
        else:  # The following is one of 3 possible orders.
            yield from visit(left)
            yield value  # Put this first or last for different orders.
            yield from visit(right)

print(list(visit(tree)))

# prints nodes in the correct order for 'yield value' in the middle.
# [1, 3, 2, 5, 4, 0, 6, 9, 8, 7]

Редактировать: заменить if tree на if tree is not None чтобы перехватывать другие ложные значения как ошибки.

Изменить 2: о размещении рекурсивных вызовов в предложении try: (комментарий @jpmc26).

Для плохих узлов приведенный выше код просто регистрирует ошибку ValueError и продолжается. Если, например, (9,None,None) заменяется на (9,None), вывод

Bad tree: (9, None)
[1, 3, 2, 5, 4, 0, 6, 8, 7]

Более типичным было бы сделать ререйз после регистрации, сделав вывод

Bad tree: (9, None)
Traceback (most recent call last):
  File "F:\Python\a\tem4.py", line 16, in <module>
    print(list(visit(tree)))
  File "F:\Python\a\tem4.py", line 14, in visit
    yield from visit(right)
  File "F:\Python\a\tem4.py", line 14, in visit
    yield from visit(right)
  File "F:\Python\a\tem4.py", line 12, in visit
    yield from visit(left)
  File "F:\Python\a\tem4.py", line 12, in visit
    yield from visit(left)
  File "F:\Python\a\tem4.py", line 7, in visit
    value, left, right = tree
ValueError: not enough values to unpack (expected 3, got 2)

В трассировке указывается путь от корня до поврежденного узла. Можно было бы обернуть исходный вызов visit(tree) чтобы уменьшить трассировку к пути: (корень, право, право, лево, слева).

Если рекурсивные вызовы включены в предложение try:, ошибка повторяется, повторно регистрируется и повторно вызывается на каждом уровне дерева.

Bad tree: (9, None)
Bad tree: (8, (9, None), None)
Bad tree: (7, (8, (9, None), None), None)
Bad tree: (6, None, (7, (8, (9, None), None), None))
Bad tree: (0, (1, None, (2, (3, None, None), (4, (5, None, None), None))), (6, None, (7, (8, (9, None), None), None)))
Traceback (most recent call last):
...  # same as before

Отчеты о множественной регистрации, скорее всего, больше шума, чем справки. Если кто-то хочет получить путь к плохому узлу, может быть проще всего обернуть каждый рекурсивный вызов в его собственное предложение try: и вызвать новый ValueError на каждом уровне, с указанным ранее созданным путем.

Вывод: если для управления потоком не используется исключение (как, например, можно сделать с помощью IndexError), наличие и размещение операторов try: зависит от сообщения об ошибке, которое вы хотите.

Ответ 4

До Python 3.4, функция-генератор раньше StopIteration исключение StopIteration когда это было сделано. Для рекурсивного случая другие исключения (например, IndexError) вызываются раньше, чем StopIteration, поэтому мы добавляем его вручную.

def recursive_generator(lis):
    if not lis: raise StopIteration
    yield lis[0]
    yield from recursive_generator(lis[1:])

for k in recursive_generator([6, 3, 9, 1]):
    print(k)

def recursive_generator(lis):
    if not lis: raise StopIteration
    yield lis.pop(0)
    yield from recursive_generator(lis)

for k in recursive_generator([6, 3, 9, 1]):
    print(k)

Обратите внимание, что цикл for будет перехватывать исключение StopIteration. Подробнее об этом здесь

Ответ 5

Да, вы можете иметь рекурсивные генераторы. Однако они страдают от того же предела глубины рекурсии, что и другие рекурсивные функции.

def recurse(x):
  yield x
  yield from recurse(x)

for (i, x) in enumerate(recurse(5)):
  print(i, x)

Этот цикл до 3000 (для меня) до сбоя.

Однако с некоторыми хитростями вы можете создать функцию, которая подает генератор на себя. Это позволяет вам писать генераторы так, как будто они рекурсивные, но не: https://gist.github.com/3noch/7969f416d403ba3a54a788b113c204ce