Как создать генератор с рекурсией?

Алгоритм от восторга Хакера 2-е издание

Портирован на питон

Простой класс кривой Гильберта.

class Hilbert():

    def __init__(self,order=1):

            self.x = -1 
            self.y = 0     
            self._step(0)
            self._hil(0, 1, order)

    def _hil(self, dirs, rot, order):
        if (order == 0): 
            return

        dirs += rot
        self._hil(dirs, -rot, (order-1))
        self._step(dirs)
        dirs -= rot
        self._hil(dirs, rot, (order-1))
        self._step(dirs)
        self._hil(dirs, rot, (order-1))
        dirs -= rot
        self._step(dirs)
        self._hil(dirs, -rot, (order-1))


    def _step(self, dirs):
        dirs %= 4

        if dirs == 0:
            self.x += 1
        elif dirs == 1:
            self.y += 1
        elif dirs == 2:
            self.x -= 1
        else:
            self.y -= 1 
        #prints all points 
        #print(self.x,self.y)
        #Could I "iterate" from here.

Так что мне нужно что-то, что дает (x,y) каждый раз, когда вызывается next(). Я пытался сделать это сам, но не могу заставить его работать, поэтому любая помощь будет оценена. Должен ли я переписать это для работы с генератором? Источник

1 ответ

Я думаю, по крайней мере, часть того, что вы пытаетесь сделать, это повернуть _hil в функцию генератора, которая дает x, y пары после каждого звонка _step? Если так, то это довольно просто.

Трудная часть - это те рекурсивные вызовы. Но это совсем не сложно. Это именно то, что yield from предназначен для:* Возьмите некоторый итератор (например, рекурсивный вызов функции генератора) и выведите каждое из его значений.**

Тогда есть легкая часть, нерекурсивная сдача x, y пары после каждого звонка _step, Вы можете сделать это с явным yield self.x, self.y после каждого звонка. Или вы можете изменить _step добавить return self.x, self.y так что вы можете просто yield self._step(dirs), Но вы также можете изменить _step итератору, который повторяет только одно значение, и тогда вы можете yield from это также. (Здесь нет никакого реального преимущества, но я думаю, что это стоит показать, чтобы вы могли продумать, как это работает, особенно после того, как вы спросили "могу ли я повторить отсюда?" В _step.)

def _hil(self, dirs, rot, order):
    if (order == 0): 
        return

    dirs += rot
    yield from self._hil(dirs, -rot, (order-1))
    yield from self._step(dirs)
    dirs -= rot
    yield from self._hil(dirs, rot, (order-1))
    yield from self._step(dirs)
    yield from self._hil(dirs, rot, (order-1))
    dirs -= rot
    yield from self._step(dirs)
    yield from self._hil(dirs, -rot, (order-1))

def _step(self, dirs):
    # existing code
    yield self.x, self.y

Но теперь у вас есть это __init__ это просто звонки _hil и ничего не делает с результатом. Это не очень полезно. Может быть, вы пытаетесь включить Hilbert сам класс в класс итератора?

В этом случае проще всего сохранить итератор генератора и делегировать ему:

def __init__(self, order=1):
    self.x = -1 
    self.y = 0     
    self._step(0)
    self.iter = self._hil(0, 1, order)

def __iter__(self):
    return self

def __next__(self):
    return next(self.iter)

Тем не менее, на данный момент, я не уверен, зачем вам это вообще нужно для занятий. x а также y на самом деле не являются частью состояния объекта, они являются частью состояния генератора, которое Python позаботится о волшебстве, если вы просто используете локальные переменные в _hil (и обычная передача параметров и возврата в _step). И единственное другое государство self.iter, что необходимо только потому, что вы пишете класс вместо функции.


* На самом деле, это хорошо для гораздо большего, как замечательно описывает Грег Юинг; у нас не было бы asyncio без этого. Но первоначальная причина для добавления его в язык заключалась в следующем: "Синтаксис для делегирования подгруппе".

** Обратите внимание, что это работает только если у вас есть yield from Означает Python 3.3 или более поздней версии. Если вы все еще используете Python 2.x, и yield from недостаточно, чтобы заставить вас обновить, вы можете смоделировать некоторые его применения, в том числе это, изменяя каждый yield from eggs в for egg in eggs: yield egg, Он не будет таким читаемым и будет значительно медленнее, но он будет работать.

Другие вопросы по тегам